mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
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:
@@ -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')
|
||||||
|
|||||||
@@ -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
@@ -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) {
|
||||||
|
const polygonArray = collectOuterRings(feature.geometry.coordinates)
|
||||||
|
polygonArray.forEach(polygon => {
|
||||||
polygons.push(
|
polygons.push(
|
||||||
poly[0].map(coord => {
|
polygon.map(coord => {
|
||||||
const lat = coord[1]
|
return updateBounds(bounds, halvedBounds, coord)
|
||||||
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]
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
countriesWithNoGeometry.push(feature.properties.name)
|
||||||
|
}
|
||||||
|
|
||||||
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
+258
File diff suppressed because one or more lines are too long
+258
File diff suppressed because one or more lines are too long
+258
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
|||||||
|
import { FrontPage as FrontPageComponent } from '@/components/FrontPage'
|
||||||
|
|
||||||
|
export const FrontPage = () => {
|
||||||
|
return <FrontPageComponent />
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user