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
+5 -241
View File
@@ -1,247 +1,11 @@
import {
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 { EditRights } from './Page'
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 = {}
const fullRights: EditRights = { new: true, edit: true, delete: true }
const limitedRights: EditRights = { new: true, edit: true }
const userHasLocalityAccess = (user: UserState, id: string | number) => {
export const noRights: EditRights = {}
export const fullRights: EditRights = { new: true, edit: true, delete: true }
export const limitedRights: EditRights = { new: true, edit: true }
export const userHasLocalityAccess = (user: UserState, id: string | number) => {
const parsedId = typeof id === 'number' ? id : parseInt(id, 10)
if (!Number.isFinite(parsedId)) return false
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',
}
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() {
if (process.argv.length != 3) {
console.log('Usage: node countryDataExportTool.js [administrative boundaries .geojson file]')
@@ -40,6 +87,8 @@ function main() {
const polygons = []
const boundingBoxes = {}
const countriesWithNoGeometry = []
borders.features.forEach(feature => {
const bounds = {
top: null,
@@ -55,35 +104,18 @@ function main() {
right: null,
}
feature.geometry.coordinates.forEach(poly => {
polygons.push(
poly[0].map(coord => {
const lat = coord[1]
const long = coord[0]
if (!bounds.top) bounds.top = lat
if (!bounds.bottom) bounds.bottom = lat
if (!bounds.left) bounds.left = long
if (!bounds.right) 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) 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]
})
)
})
if (feature.geometry) {
const polygonArray = collectOuterRings(feature.geometry.coordinates)
polygonArray.forEach(polygon => {
polygons.push(
polygon.map(coord => {
return updateBounds(bounds, halvedBounds, coord)
})
)
})
} else {
countriesWithNoGeometry.push(feature.properties.name)
}
let country = feature.properties.name
if (country in nameMappings) country = nameMappings[country]
@@ -112,6 +144,13 @@ export type CountryBoundingBoxes =
boundsType + 'export const countryBoundingBoxes: CountryBoundingBoxes = ' + JSON.stringify(boundingBoxes) + ';'
fs.writeFileSync('countryPolygons.ts', polygonString)
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()
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 { 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([
{
path: '/',
@@ -31,22 +11,87 @@ const router = createBrowserRouter([
{
index: true,
lazy: async () => {
const { FrontPage } = await import('../components/FrontPage')
const { FrontPage } = await import('../pages/FrontPage')
return { Component: FrontPage }
},
},
{ path: 'occurrence/:lid/:speciesId', lazy: () => loadPagesElement('crossSearchPage') },
{ path: 'occurrence/:id', element: <Navigate to="/occurrence" replace /> },
{ path: 'occurrence', lazy: () => loadPagesElement('crossSearchPage') },
{ path: 'crosssearch/:id?', lazy: () => loadPagesElement('crossSearchPage') },
{ path: 'locality/:id?', lazy: () => loadPagesElement('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: 'time-bound/:id?', lazy: () => loadPagesElement('timeBoundPage') },
{ path: 'region/:id?', lazy: () => loadPagesElement('regionPage') },
{ path: 'person/:id?', lazy: () => loadPagesElement('personPage') },
{
path: 'locality/:id?',
lazy: async () => {
const { LocalityPage } = await import('../pages/LocalityPage')
return { Component: LocalityPage }
},
},
{
path: 'species/:id?',
lazy: async () => {
const { SpeciesPage } = await import('../pages/SpeciesPage')
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',
lazy: async () => {
@@ -61,7 +106,6 @@ const router = createBrowserRouter([
return { Component: ProjectEditPage }
},
},
{ path: 'project/:id?', lazy: () => loadPagesElement('projectPage') },
{
path: 'email',
lazy: async () => {