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', () => {
|
||||
cy.visit('/')
|
||||
cy.visit('/crosssearch')
|
||||
cy.url().should('include', '/crosssearch')
|
||||
cy.visit('/occurrence')
|
||||
cy.url().should('include', '/occurrence')
|
||||
cy.contains('Occurrences').should('be.visible')
|
||||
cy.contains('Country').should('be.visible')
|
||||
cy.contains('Genus').should('be.visible')
|
||||
|
||||
@@ -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
@@ -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
+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 { 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 () => {
|
||||
|
||||
Reference in New Issue
Block a user