Improve loading times by splitting pages.tsx into smaller files and improving the locality map (#1224)

* split pages.tsx into multiple files, and update ui e2e test since occurrence rename

* reduce size of countryBoundingBoxes and countryPolygons

* update files again, and update countryExport script

* update files again, add more simplified geojson files, and update countryExport with Copilot
This commit is contained in:
juhanikat
2026-08-04 13:51:13 +03:00
committed by GitHub
parent fa6dc11fee
commit e9fa3e3b31
20 changed files with 1231 additions and 309 deletions
+2 -2
View File
@@ -49,8 +49,8 @@ describe('Button Tests', () => {
it('Cross Search button works', () => { it('Cross Search button works', () => {
cy.visit('/') cy.visit('/')
cy.visit('/crosssearch') cy.visit('/occurrence')
cy.url().should('include', '/crosssearch') cy.url().should('include', '/occurrence')
cy.contains('Occurrences').should('be.visible') cy.contains('Occurrences').should('be.visible')
cy.contains('Country').should('be.visible') cy.contains('Country').should('be.visible')
cy.contains('Genus').should('be.visible') cy.contains('Genus').should('be.visible')
+5 -241
View File
@@ -1,247 +1,11 @@
import { import { EditRights } from './Page'
TimeUnitDetailsType,
SpeciesDetailsType,
LocalityDetailsType,
TimeBoundDetailsType,
PersonDetailsType,
OccurrenceDetailsType,
ProjectDetailsType,
RegionDetails as RegionDetailsType,
Role,
Museum,
} from '@/shared/types'
import { CrossSearchTable } from './CrossSearch/CrossSearchTable'
import { LocalityDetails } from './Locality/LocalityDetails'
import { LocalityTable } from './Locality/LocalityTable'
import { EditRights, Page } from './Page'
import { ReferenceTable } from './Reference/ReferenceTable'
import { SpeciesDetails } from './Species/SpeciesDetails'
import { SpeciesTable } from './Species/SpeciesTable'
import { TimeBoundDetails } from './TimeBound/TimeBoundDetails'
import { TimeBoundTable } from './TimeBound/TimeBoundTable'
import { TimeUnitTable } from './TimeUnit/TimeUnitTable'
import { TimeUnitDetails } from './TimeUnit/TimeUnitDetails'
import { ReferenceDetails } from './Reference/ReferenceDetails'
import { PersonListPage } from '@/pages/PersonListPage'
import { PersonDetailPage } from '@/pages/PersonDetailPage'
import { ProjectTable } from './Project/ProjectTable'
import { ProjectDetails } from './Project/ProjectDetails'
import { RegionTable } from './Region/RegionTable'
import { RegionDetails } from './Region/RegionDetails'
import { UserState } from '@/redux/userReducer' import { UserState } from '@/redux/userReducer'
import { FrontPage } from './FrontPage'
import { MuseumTable } from './Museum/MuseumTable'
import { MuseumDetails } from './Museum/MuseumDetails'
import { createReferenceSubtitle, createReferenceTitle } from './Reference/referenceFormatting'
import { UnsavedChangesProvider } from './UnsavedChangesProvider'
import { OccurrenceDetails } from './Occurrence/OccurrenceDetails'
const noRights: EditRights = {} export const noRights: EditRights = {}
const fullRights: EditRights = { new: true, edit: true, delete: true } export const fullRights: EditRights = { new: true, edit: true, delete: true }
const limitedRights: EditRights = { new: true, edit: true } export const limitedRights: EditRights = { new: true, edit: true }
const userHasLocalityAccess = (user: UserState, id: string | number) => { export const userHasLocalityAccess = (user: UserState, id: string | number) => {
const parsedId = typeof id === 'number' ? id : parseInt(id, 10) const parsedId = typeof id === 'number' ? id : parseInt(id, 10)
if (!Number.isFinite(parsedId)) return false if (!Number.isFinite(parsedId)) return false
return user.localities.includes(parsedId) return user.localities.includes(parsedId)
} }
export const localityPage = (
<UnsavedChangesProvider>
<Page
tableView={<LocalityTable />}
detailView={<LocalityDetails wrapWithUnsavedChangesProvider={false} />}
viewName="locality"
idFieldName="lid"
createTitle={(loc: LocalityDetailsType) => `${loc.lid} ${loc.loc_name}, ${loc.country}`}
createSubtitle={(loc: LocalityDetailsType) =>
`${loc.dms_lat}, ${loc.dms_long}\n${loc.max_age} Ma (${loc.bfa_max}) – ${loc.min_age} Ma (${loc.bfa_min})`
}
getEditRights={(user: UserState, id: string | number) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) {
if (id === '' || id === 'new') return { new: true }
if (userHasLocalityAccess(user, id)) return limitedRights
}
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const frontPage = <FrontPage />
export const crossSearchPage = (
<Page
tableView={<CrossSearchTable />}
detailView={<OccurrenceDetails />}
viewName="occurrence"
idFieldName="lid"
createTitle={(occurrence: OccurrenceDetailsType) => {
const genusSpecies = `${occurrence.genus_name} ${occurrence.species_name}`.trim()
const hasUsefulUniqueIdentifier =
!!occurrence.unique_identifier &&
occurrence.unique_identifier.trim() !== '-' &&
occurrence.unique_identifier.trim() !== ''
const speciesDisplay = genusSpecies || (hasUsefulUniqueIdentifier ? occurrence.unique_identifier : '-')
return `${occurrence.lid} ${occurrence.loc_name}, ${occurrence.country}
${speciesDisplay} (species_id: ${occurrence.species_id})`
}}
createSubtitle={(occurrence: OccurrenceDetailsType) =>
`${occurrence.dms_lat ?? ''}, ${occurrence.dms_long ?? ''}
${occurrence.max_age ?? ''} Ma (${occurrence.bfa_max ?? ''}) – ${occurrence.min_age ?? ''} Ma (${occurrence.bfa_min ?? ''})`
}
getEditRights={(user: UserState, id: string | number) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted && userHasLocalityAccess(user, id)) return limitedRights
return noRights
}}
/>
)
export const speciesPage = (
<UnsavedChangesProvider>
<Page
tableView={<SpeciesTable />}
detailView={<SpeciesDetails wrapWithUnsavedChangesProvider={false} />}
viewName="species"
idFieldName="species_id"
createTitle={(species: SpeciesDetailsType) =>
`${species.species_id} ${species.genus_name} ${species.species_name}` + `\n${species.unique_identifier}`
}
createSubtitle={(species: SpeciesDetailsType) =>
`Order ${species.order_name}` +
`\nFamily ${species.family_name}` +
`\nSubfamily or Tribe ${species.subfamily_name}`
}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const museumPage = (
<UnsavedChangesProvider>
<Page
tableView={<MuseumTable />}
detailView={<MuseumDetails wrapWithUnsavedChangesProvider={false} />}
viewName="museum"
idFieldName="museum"
createTitle={(museum: Museum) => `${museum.institution}`}
createSubtitle={(museum: Museum) => `${museum.city ? `${museum.city}, ` : ''}${museum.country}`}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const referencePage = (
<UnsavedChangesProvider>
<Page
tableView={<ReferenceTable />}
detailView={<ReferenceDetails wrapWithUnsavedChangesProvider={false} />}
viewName="reference"
idFieldName="rid"
createTitle={createReferenceTitle}
createSubtitle={createReferenceSubtitle}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
if (user.role === Role.EditUnrestricted) return limitedRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const timeUnitPage = (
<UnsavedChangesProvider>
<Page
tableView={<TimeUnitTable />}
detailView={<TimeUnitDetails wrapWithUnsavedChangesProvider={false} />}
viewName="time-unit"
idFieldName="tu_name"
createTitle={(tu: TimeUnitDetailsType) => `${tu.tu_display_name}${tu.tu_comment ? ` - ${tu.tu_comment}` : ''}`}
createSubtitle={(tu: TimeUnitDetailsType) =>
`${tu.up_bound?.age} Ma \u2013 ${tu.low_bound?.age} Ma` +
`\n${tu.now_tu_sequence.seq_name}` +
` (${tu.rank ? tu.rank : 'No rank'})`
}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const timeBoundPage = (
<UnsavedChangesProvider>
<Page
allowedRoles={[Role.Admin, Role.EditUnrestricted]}
tableView={<TimeBoundTable />}
detailView={<TimeBoundDetails wrapWithUnsavedChangesProvider={false} />}
viewName="time-bound"
idFieldName="bid"
createTitle={(tb: TimeBoundDetailsType) => `${tb.bid} ${tb.b_name}`}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
export const personPage = (
// Only admins are allowed to see this page, but the rights cannot be checked here through
// allowedRoles, as the component has to be rendered for the user's own user-page.
// This is ok because the requests should fail on backend anyways.
<Page
tableView={<PersonListPage />}
detailView={<PersonDetailPage />}
viewName="person"
idFieldName="initials"
createTitle={(person: PersonDetailsType) => `${person.surname}`}
getEditRights={(user: UserState, id: number | string) => {
if (user.role === Role.Admin) return fullRights
if (user.initials === id || id === 'user-page') return { edit: true }
return noRights
}}
/>
)
export const projectPage = (
<Page
allowedRoles={[Role.Admin]}
tableView={<ProjectTable />}
detailView={<ProjectDetails />}
viewName="project"
idFieldName="pid"
createTitle={(project: ProjectDetailsType) => `${project.proj_name}`}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
return noRights
}}
/>
)
export const regionPage = (
<Page<RegionDetailsType>
allowedRoles={[Role.Admin]}
tableView={<RegionTable />}
detailView={<RegionDetails />}
viewName="region"
idFieldName="reg_coord_id"
createTitle={(region: RegionDetailsType) => `${region.region}`}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
return noRights
}}
/>
)
File diff suppressed because one or more lines are too long
+68 -29
View File
@@ -28,6 +28,53 @@ const nameMappings = {
'United States Virgin Islands': 'Virgin Islands', 'United States Virgin Islands': 'Virgin Islands',
} }
function isCoordinatePair(value) {
return Array.isArray(value) && value.length >= 2 && typeof value[0] === 'number' && typeof value[1] === 'number'
}
function isLinearRing(value) {
return Array.isArray(value) && value.length > 0 && isCoordinatePair(value[0])
}
function collectOuterRings(value) {
if (!Array.isArray(value) || value.length === 0) {
return []
}
if (isLinearRing(value)) {
return [value]
}
if (Array.isArray(value[0]) && isLinearRing(value[0])) {
return [value[0]]
}
return value.flatMap(collectOuterRings)
}
function updateBounds(bounds, halvedBounds, coord) {
const lat = coord[1]
const long = coord[0]
if (bounds.top === null) bounds.top = lat
if (bounds.bottom === null) bounds.bottom = lat
if (bounds.left === null) bounds.left = long
if (bounds.right === null) bounds.right = long
bounds.top = Math.min(bounds.top, lat)
bounds.bottom = Math.max(bounds.bottom, lat)
bounds.left = Math.min(bounds.left, long)
bounds.right = Math.max(bounds.right, long)
if (halvedBounds.top === null) halvedBounds.top = lat
if (halvedBounds.bottom === null) halvedBounds.bottom = lat
if (halvedBounds.right === null) halvedBounds.right = -180
if (halvedBounds.left === null) halvedBounds.left = long
halvedBounds.top = Math.min(halvedBounds.top, lat)
halvedBounds.bottom = Math.max(halvedBounds.bottom, lat)
if (long < 0) halvedBounds.right = Math.max(halvedBounds.right, long)
else halvedBounds.left = Math.min(halvedBounds.left, long)
return [lat, long]
}
function main() { function main() {
if (process.argv.length != 3) { if (process.argv.length != 3) {
console.log('Usage: node countryDataExportTool.js [administrative boundaries .geojson file]') console.log('Usage: node countryDataExportTool.js [administrative boundaries .geojson file]')
@@ -40,6 +87,8 @@ function main() {
const polygons = [] const polygons = []
const boundingBoxes = {} const boundingBoxes = {}
const countriesWithNoGeometry = []
borders.features.forEach(feature => { borders.features.forEach(feature => {
const bounds = { const bounds = {
top: null, top: null,
@@ -55,35 +104,18 @@ function main() {
right: null, right: null,
} }
feature.geometry.coordinates.forEach(poly => { if (feature.geometry) {
polygons.push( const polygonArray = collectOuterRings(feature.geometry.coordinates)
poly[0].map(coord => { polygonArray.forEach(polygon => {
const lat = coord[1] polygons.push(
const long = coord[0] polygon.map(coord => {
return updateBounds(bounds, halvedBounds, coord)
if (!bounds.top) bounds.top = lat })
if (!bounds.bottom) bounds.bottom = lat )
if (!bounds.left) bounds.left = long })
if (!bounds.right) bounds.right = long } else {
bounds.top = Math.min(bounds.top, lat) countriesWithNoGeometry.push(feature.properties.name)
bounds.bottom = Math.max(bounds.bottom, lat) }
bounds.left = Math.min(bounds.left, long)
bounds.right = Math.max(bounds.right, long)
if (!halvedBounds.top) halvedBounds.top = lat
if (!halvedBounds.bottom) halvedBounds.bottom = lat
if (!halvedBounds.right) halvedBounds.right = -180
if (!halvedBounds.left) halvedBounds.left = long
halvedBounds.top = Math.min(halvedBounds.top, lat)
halvedBounds.bottom = Math.max(halvedBounds.bottom, lat)
if (long < 0) halvedBounds.right = Math.max(halvedBounds.right, long)
else halvedBounds.left = Math.min(halvedBounds.left, long)
return [lat, long]
})
)
})
let country = feature.properties.name let country = feature.properties.name
if (country in nameMappings) country = nameMappings[country] if (country in nameMappings) country = nameMappings[country]
@@ -112,6 +144,13 @@ export type CountryBoundingBoxes =
boundsType + 'export const countryBoundingBoxes: CountryBoundingBoxes = ' + JSON.stringify(boundingBoxes) + ';' boundsType + 'export const countryBoundingBoxes: CountryBoundingBoxes = ' + JSON.stringify(boundingBoxes) + ';'
fs.writeFileSync('countryPolygons.ts', polygonString) fs.writeFileSync('countryPolygons.ts', polygonString)
fs.writeFileSync('countryBoundingBoxes.ts', boundsString) fs.writeFileSync('countryBoundingBoxes.ts', boundsString)
if (countriesWithNoGeometry.length > 0) {
console.log(
'The following countries have no geometry in the supplied .geojson file (probably due to too much simplification in mapshaper): '
)
console.log(countriesWithNoGeometry.join(', '))
}
} }
main() main()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
import { FrontPage as FrontPageComponent } from '@/components/FrontPage'
export const FrontPage = () => {
return <FrontPageComponent />
}
+33
View File
@@ -0,0 +1,33 @@
import { LocalityDetails } from '@/components/Locality/LocalityDetails'
import { LocalityTable } from '@/components/Locality/LocalityTable'
import { fullRights, limitedRights, noRights, userHasLocalityAccess } from '@/components/pages'
import { UserState } from '@/redux/userReducer'
import { LocalityDetailsType, Role } from '@/shared/types'
import { Page } from '../components/Page'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const LocalityPage = () => {
return (
<UnsavedChangesProvider>
<Page
tableView={<LocalityTable />}
detailView={<LocalityDetails wrapWithUnsavedChangesProvider={false} />}
viewName="locality"
idFieldName="lid"
createTitle={(loc: LocalityDetailsType) => `${loc.lid} ${loc.loc_name}, ${loc.country}`}
createSubtitle={(loc: LocalityDetailsType) =>
`${loc.dms_lat}, ${loc.dms_long}\n${loc.max_age} Ma (${loc.bfa_max}) – ${loc.min_age} Ma (${loc.bfa_min})`
}
getEditRights={(user: UserState, id: string | number) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) {
if (id === '' || id === 'new') return { new: true }
if (userHasLocalityAccess(user, id)) return limitedRights
}
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+28
View File
@@ -0,0 +1,28 @@
import { MuseumDetails } from '@/components/Museum/MuseumDetails'
import { MuseumTable } from '@/components/Museum/MuseumTable'
import { fullRights, limitedRights, noRights } from '@/components/pages'
import { UserState } from '@/redux/userReducer'
import { Museum, Role } from '@/shared/types'
import { Page } from '../components/Page'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const MuseumPage = () => {
return (
<UnsavedChangesProvider>
<Page
tableView={<MuseumTable />}
detailView={<MuseumDetails wrapWithUnsavedChangesProvider={false} />}
viewName="museum"
idFieldName="museum"
createTitle={(museum: Museum) => `${museum.institution}`}
createSubtitle={(museum: Museum) => `${museum.city ? `${museum.city}, ` : ''}${museum.country}`}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+38
View File
@@ -0,0 +1,38 @@
import { CrossSearchTable } from '@/components/CrossSearch/CrossSearchTable'
import { OccurrenceDetails } from '@/components/Occurrence/OccurrenceDetails'
import { fullRights, limitedRights, noRights, userHasLocalityAccess } from '@/components/pages'
import { UserState } from '@/redux/userReducer'
import { OccurrenceDetailsType, Role } from '@/shared/types'
import { Page } from '../components/Page'
export const OccurrencesPage = () => {
return (
<Page
tableView={<CrossSearchTable />}
detailView={<OccurrenceDetails />}
viewName="occurrence"
idFieldName="lid"
createTitle={(occurrence: OccurrenceDetailsType) => {
const genusSpecies = `${occurrence.genus_name} ${occurrence.species_name}`.trim()
const hasUsefulUniqueIdentifier =
!!occurrence.unique_identifier &&
occurrence.unique_identifier.trim() !== '-' &&
occurrence.unique_identifier.trim() !== ''
const speciesDisplay = genusSpecies || (hasUsefulUniqueIdentifier ? occurrence.unique_identifier : '-')
return `${occurrence.lid} ${occurrence.loc_name}, ${occurrence.country}
${speciesDisplay} (species_id: ${occurrence.species_id})`
}}
createSubtitle={(occurrence: OccurrenceDetailsType) =>
`${occurrence.dms_lat ?? ''}, ${occurrence.dms_long ?? ''}
${occurrence.max_age ?? ''} Ma (${occurrence.bfa_max ?? ''}) – ${occurrence.min_age ?? ''} Ma (${occurrence.bfa_min ?? ''})`
}
getEditRights={(user: UserState, id: string | number) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted && userHasLocalityAccess(user, id)) return limitedRights
return noRights
}}
/>
)
}
+27
View File
@@ -0,0 +1,27 @@
import { fullRights, noRights } from '@/components/pages'
import { UserState } from '@/redux/userReducer'
import { PersonDetailsType, Role } from '@/shared/types'
import { Page } from '../components/Page'
import { PersonDetailPage } from './PersonDetailPage'
import { PersonListPage } from './PersonListPage'
export const PersonPage = () => {
return (
// Only admins are allowed to see this page, but the rights cannot be checked here through
// allowedRoles, as the component has to be rendered for the user's own user-page.
// This is ok because the requests should fail on backend anyways.
<Page
tableView={<PersonListPage />}
detailView={<PersonDetailPage />}
viewName="person"
idFieldName="initials"
createTitle={(person: PersonDetailsType) => `${person.surname}`}
getEditRights={(user: UserState, id: number | string) => {
if (user.role === Role.Admin) return fullRights
if (user.initials === id || id === 'user-page') return { edit: true }
return noRights
}}
/>
)
}
+24
View File
@@ -0,0 +1,24 @@
import { fullRights, noRights } from '@/components/pages'
import { ProjectDetails } from '@/components/Project/ProjectDetails'
import { ProjectTable } from '@/components/Project/ProjectTable'
import { UserState } from '@/redux/userReducer'
import { ProjectDetailsType, Role } from '@/shared/types'
import { Page } from '../components/Page'
export const ProjectPage = () => {
return (
<Page
allowedRoles={[Role.Admin]}
tableView={<ProjectTable />}
detailView={<ProjectDetails />}
viewName="project"
idFieldName="pid"
createTitle={(project: ProjectDetailsType) => `${project.proj_name}`}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
return noRights
}}
/>
)
}
+30
View File
@@ -0,0 +1,30 @@
import { fullRights, limitedRights, noRights } from '@/components/pages'
import { ReferenceDetails } from '@/components/Reference/ReferenceDetails'
import { ReferenceTable } from '@/components/Reference/ReferenceTable'
import { UserState } from '@/redux/userReducer'
import { Role } from '@/shared/types'
import { Page } from '../components/Page'
import { createReferenceSubtitle, createReferenceTitle } from '../components/Reference/referenceFormatting'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const ReferencePage = () => {
return (
<UnsavedChangesProvider>
<Page
tableView={<ReferenceTable />}
detailView={<ReferenceDetails wrapWithUnsavedChangesProvider={false} />}
viewName="reference"
idFieldName="rid"
createTitle={createReferenceTitle}
createSubtitle={createReferenceSubtitle}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
if (user.role === Role.EditUnrestricted) return limitedRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+24
View File
@@ -0,0 +1,24 @@
import { fullRights, noRights } from '@/components/pages'
import { RegionDetails } from '@/components/Region/RegionDetails'
import { RegionTable } from '@/components/Region/RegionTable'
import { UserState } from '@/redux/userReducer'
import { RegionDetails as RegionDetailsType, Role } from '@/shared/types'
import { Page } from '../components/Page'
export const RegionPage = () => {
return (
<Page<RegionDetailsType>
allowedRoles={[Role.Admin]}
tableView={<RegionTable />}
detailView={<RegionDetails />}
viewName="region"
idFieldName="reg_coord_id"
createTitle={(region: RegionDetailsType) => `${region.region}`}
getEditRights={(user: UserState) => {
if (user.role === Role.Admin) return fullRights
return noRights
}}
/>
)
}
+34
View File
@@ -0,0 +1,34 @@
import { fullRights, limitedRights, noRights } from '@/components/pages'
import { UserState } from '@/redux/userReducer'
import { Role, SpeciesDetailsType } from '@/shared/types'
import { Page } from '../components/Page'
import { SpeciesDetails } from '../components/Species/SpeciesDetails'
import { SpeciesTable } from '../components/Species/SpeciesTable'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const SpeciesPage = () => {
return (
<UnsavedChangesProvider>
<Page
tableView={<SpeciesTable />}
detailView={<SpeciesDetails wrapWithUnsavedChangesProvider={false} />}
viewName="species"
idFieldName="species_id"
createTitle={(species: SpeciesDetailsType) =>
`${species.species_id} ${species.genus_name} ${species.species_name}` + `\n${species.unique_identifier}`
}
createSubtitle={(species: SpeciesDetailsType) =>
`Order ${species.order_name}` +
`\nFamily ${species.family_name}` +
`\nSubfamily or Tribe ${species.subfamily_name}`
}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
if (user.role === Role.EditRestricted) return limitedRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+27
View File
@@ -0,0 +1,27 @@
import { fullRights, noRights } from '@/components/pages'
import { TimeBoundDetails } from '@/components/TimeBound/TimeBoundDetails'
import { TimeBoundTable } from '@/components/TimeBound/TimeBoundTable'
import { UserState } from '@/redux/userReducer'
import { Role, TimeBoundDetailsType } from '@/shared/types'
import { Page } from '../components/Page'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const TimeBoundPage = () => {
return (
<UnsavedChangesProvider>
<Page
allowedRoles={[Role.Admin, Role.EditUnrestricted]}
tableView={<TimeBoundTable />}
detailView={<TimeBoundDetails wrapWithUnsavedChangesProvider={false} />}
viewName="time-bound"
idFieldName="bid"
createTitle={(tb: TimeBoundDetailsType) => `${tb.bid} ${tb.b_name}`}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+31
View File
@@ -0,0 +1,31 @@
import { fullRights, noRights } from '@/components/pages'
import { TimeUnitDetails } from '@/components/TimeUnit/TimeUnitDetails'
import { TimeUnitTable } from '@/components/TimeUnit/TimeUnitTable'
import { UserState } from '@/redux/userReducer'
import { Role, TimeUnitDetailsType } from '@/shared/types'
import { Page } from '../components/Page'
import { UnsavedChangesProvider } from '../components/UnsavedChangesProvider'
export const TimeUnitPage = () => {
return (
<UnsavedChangesProvider>
<Page
tableView={<TimeUnitTable />}
detailView={<TimeUnitDetails wrapWithUnsavedChangesProvider={false} />}
viewName="time-unit"
idFieldName="tu_name"
createTitle={(tu: TimeUnitDetailsType) => `${tu.tu_display_name}${tu.tu_comment ? ` - ${tu.tu_comment}` : ''}`}
createSubtitle={(tu: TimeUnitDetailsType) =>
`${tu.up_bound?.age} Ma \u2013 ${tu.low_bound?.age} Ma` +
`\n${tu.now_tu_sequence.seq_name}` +
` (${tu.rank ? tu.rank : 'No rank'})`
}
getEditRights={(user: UserState) => {
if ([Role.Admin, Role.EditUnrestricted].includes(user.role)) return fullRights
return noRights
}}
/>
</UnsavedChangesProvider>
)
}
+79 -35
View File
@@ -1,27 +1,7 @@
import { Navigate, createBrowserRouter } from 'react-router-dom' import { createBrowserRouter } from 'react-router-dom'
import App from '../App' import App from '../App'
import { RouterError } from '../components/ErrorBoundary/RouterError' import { RouterError } from '../components/ErrorBoundary/RouterError'
const loadPagesElement = async (
key:
| 'crossSearchPage'
| 'localityPage'
| 'museumPage'
| 'personPage'
| 'projectPage'
| 'referencePage'
| 'regionPage'
| 'speciesPage'
| 'timeBoundPage'
| 'timeUnitPage'
) => {
const pagesModule = await import('../components/pages')
return {
Component: () => pagesModule[key],
}
}
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: '/', path: '/',
@@ -31,22 +11,87 @@ const router = createBrowserRouter([
{ {
index: true, index: true,
lazy: async () => { lazy: async () => {
const { FrontPage } = await import('../components/FrontPage') const { FrontPage } = await import('../pages/FrontPage')
return { Component: FrontPage } return { Component: FrontPage }
}, },
}, },
{ path: 'occurrence/:lid/:speciesId', lazy: () => loadPagesElement('crossSearchPage') }, {
{ path: 'occurrence/:id', element: <Navigate to="/occurrence" replace /> }, path: 'locality/:id?',
{ path: 'occurrence', lazy: () => loadPagesElement('crossSearchPage') }, lazy: async () => {
{ path: 'crosssearch/:id?', lazy: () => loadPagesElement('crossSearchPage') }, const { LocalityPage } = await import('../pages/LocalityPage')
{ path: 'locality/:id?', lazy: () => loadPagesElement('localityPage') }, return { Component: LocalityPage }
{ path: 'species/:id?', lazy: () => loadPagesElement('speciesPage') }, },
{ path: 'museum/:id?', lazy: () => loadPagesElement('museumPage') }, },
{ path: 'reference/:id?', lazy: () => loadPagesElement('referencePage') }, {
{ path: 'time-unit/:id?', lazy: () => loadPagesElement('timeUnitPage') }, path: 'species/:id?',
{ path: 'time-bound/:id?', lazy: () => loadPagesElement('timeBoundPage') }, lazy: async () => {
{ path: 'region/:id?', lazy: () => loadPagesElement('regionPage') }, const { SpeciesPage } = await import('../pages/SpeciesPage')
{ path: 'person/:id?', lazy: () => loadPagesElement('personPage') }, return { Component: SpeciesPage }
},
},
{
path: 'occurrence/:lid/:speciesId',
lazy: async () => {
const { OccurrencesPage } = await import('../pages/OccurrencesPage')
return { Component: OccurrencesPage }
},
},
{
path: 'occurrence',
lazy: async () => {
const { OccurrencesPage } = await import('../pages/OccurrencesPage')
return { Component: OccurrencesPage }
},
},
{
path: 'museum/:id?',
lazy: async () => {
const { MuseumPage } = await import('../pages/MuseumPage')
return { Component: MuseumPage }
},
},
{
path: 'reference/:id?',
lazy: async () => {
const { ReferencePage } = await import('../pages/ReferencePage')
return { Component: ReferencePage }
},
},
{
path: 'time-unit/:id?',
lazy: async () => {
const { TimeUnitPage } = await import('../pages/TimeUnitPage')
return { Component: TimeUnitPage }
},
},
{
path: 'time-bound/:id?',
lazy: async () => {
const { TimeBoundPage } = await import('../pages/TimeBoundPage')
return { Component: TimeBoundPage }
},
},
{
path: 'region/:id?',
lazy: async () => {
const { RegionPage } = await import('../pages/RegionPage')
return { Component: RegionPage }
},
},
{
path: 'person/:id?',
lazy: async () => {
const { PersonPage } = await import('../pages/PersonPage')
return { Component: PersonPage }
},
},
{
path: 'project/:id?',
lazy: async () => {
const { ProjectPage } = await import('../pages/ProjectPage')
return { Component: ProjectPage }
},
},
{ {
path: 'project/new', path: 'project/new',
lazy: async () => { lazy: async () => {
@@ -61,7 +106,6 @@ const router = createBrowserRouter([
return { Component: ProjectEditPage } return { Component: ProjectEditPage }
}, },
}, },
{ path: 'project/:id?', lazy: () => loadPagesElement('projectPage') },
{ {
path: 'email', path: 'email',
lazy: async () => { lazy: async () => {