mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge'd week 24, but person/user things still are mixed and need clarifying
This commit is contained in:
@@ -6,6 +6,7 @@ import museumRouter from './routes/museum'
|
||||
import referenceRouter from './routes/reference'
|
||||
import sedimentaryStructureRouter from './routes/sedimentaryStructure'
|
||||
import regionRouter from './routes/region'
|
||||
import personRouter from './routes/person'
|
||||
import projectRouter from './routes/project'
|
||||
import speciesRouter from './routes/species'
|
||||
import timeBoundRouter from './routes/timeBound'
|
||||
@@ -35,6 +36,7 @@ if (BACKEND_MODE !== 'dev') app.use(requireLogin)
|
||||
app.use('/locality', localityRouter)
|
||||
app.use('/reference', referenceRouter)
|
||||
app.use('/region', regionRouter)
|
||||
app.use('/person', personRouter)
|
||||
app.use('/project', projectRouter)
|
||||
app.use('/species', speciesRouter)
|
||||
app.use('/time-bound', timeBoundRouter)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Router } from 'express'
|
||||
import { getAllPersons, getPersonDetails } from '../services/person'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/all', async (_req, res) => {
|
||||
const persons = await getAllPersons()
|
||||
return res.status(200).send(persons)
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const id = req.params.id
|
||||
const person = await getPersonDetails(id)
|
||||
res.status(200).send(person)
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -4,6 +4,7 @@ import * as jwt from 'jsonwebtoken'
|
||||
import { SECRET, LOGIN_VALID_MS, USER_CREATION_SECRET } from '../utils/config'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import { nowDb } from '../utils/db'
|
||||
import { getAllUsers, getUserDetails } from '../services/user'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -27,6 +28,17 @@ router.post('/login', async (req, res) => {
|
||||
return res.status(200).send({ token, username: result.user_name })
|
||||
})
|
||||
|
||||
router.get('/all', async (_req, res) => {
|
||||
const users = await getAllUsers()
|
||||
return res.status(200).send(users)
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const id = parseInt(req.params.id)
|
||||
const user = await getUserDetails(id)
|
||||
res.status(200).send(user)
|
||||
})
|
||||
|
||||
router.post('/create', async (req, res) => {
|
||||
// TODO type guards
|
||||
const { username, password, secret } = req.body
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { nowDb } from '../utils/db'
|
||||
|
||||
export const getAllPersons = async () => {
|
||||
const result = await nowDb.com_people.findMany({})
|
||||
return result
|
||||
}
|
||||
|
||||
export const getPersonDetails = async (id: string) => {
|
||||
// TODO: Check if user has access
|
||||
|
||||
const result = await nowDb.com_people.findUnique({
|
||||
where: { initials: id },
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -26,3 +26,14 @@ export const getAllUsers = async () => {
|
||||
const result = await nowDb.com_users.findMany({})
|
||||
return result
|
||||
}
|
||||
|
||||
export const getUserDetails = async (id: number) => {
|
||||
// TODO: Check if user has access
|
||||
|
||||
const result = await nowDb.com_users.findUnique({
|
||||
where: {
|
||||
user_id: id,
|
||||
},
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import { Login } from './components/Login'
|
||||
import {
|
||||
localityPage,
|
||||
personPage,
|
||||
projectPage,
|
||||
referencePage,
|
||||
regionPage,
|
||||
speciesPage,
|
||||
timeBoundPage,
|
||||
timeUnitPage,
|
||||
userPage,
|
||||
} from './components/pages'
|
||||
|
||||
const App = () => {
|
||||
@@ -31,7 +33,9 @@ const App = () => {
|
||||
<Route element={timeUnitPage} path="/time-unit/:id?" />
|
||||
<Route element={timeBoundPage} path="/time-bound/:id?" />
|
||||
<Route element={regionPage} path="/region/:id?" />
|
||||
<Route element={personPage} path="/person/:id?" />
|
||||
<Route element={projectPage} path="/project/:id?" />
|
||||
<Route element={userPage} path="/user/:id?" />
|
||||
<Route element={<FrontPage />} path="/" />
|
||||
<Route element={<Login />} path="/login" />
|
||||
<Route element={<div>Page not found.</div>} path="*" />
|
||||
|
||||
Vendored
+6
-3
@@ -30,6 +30,7 @@ export type SpeciesLocality = Prisma.now_ls & { now_loc: Prisma.now_loc }
|
||||
export type LocalityUpdate = Prisma.now_lau & { now_lr: LocalityReference[] } & { updates: UpdateLog[] }
|
||||
export type SpeciesUpdate = Prisma.now_sau & { now_sr: Prisma.now_sr }
|
||||
export type Museum = Prisma.com_mlist
|
||||
export type PersonDetailsType = Prisma.com_people
|
||||
export type ProjectPeople = Prisma.now_proj_people
|
||||
export type ProjectDetailsType = Prisma.now_proj & { now_proj_people: Array<ProjectPeople> }
|
||||
export type Project = Prisma.now_proj
|
||||
@@ -142,10 +143,12 @@ export type TimeBoundUpdate = Prisma.now_bau & { now_br: Prisma.now_br }
|
||||
export type TimeUnitSequence = Prisma.now_tu_sequence
|
||||
|
||||
export type TimeUnitUpdate = Prisma.now_time_update & { now_tr: Prisma.now_tr }
|
||||
export type TimeUnitDetailsType = EditDataType<
|
||||
Prisma.now_time_unit & { now_tu_sequence: Array<SequenceDetailsType> }
|
||||
> & { now_tau: Array<TimeUnitUpdate> }
|
||||
export type TimeUnitDetailsType = Prisma.now_time_unit & { now_tu_sequence: Array<SequenceDetailsType> } & {
|
||||
now_tau: Array<TimeUnitUpdate>
|
||||
}
|
||||
|
||||
export type ReferenceType = Prisma.ref_ref_type & { ref_field_name: Prisma.ref_field_name[] }
|
||||
|
||||
export type ReferenceField = Prisma.ref_field_name
|
||||
export type User = Prisma.com_users
|
||||
export type UserDetailsType = Prisma.com_users
|
||||
|
||||
@@ -52,14 +52,14 @@ export const LocalityDetails = () => {
|
||||
title: 'Climate',
|
||||
content: <ClimateTab />,
|
||||
},
|
||||
{
|
||||
title: 'Archaeology',
|
||||
content: <ArchaeologyTab />,
|
||||
},
|
||||
{
|
||||
title: 'Ecometrics',
|
||||
content: <EcometricsTab />,
|
||||
},
|
||||
{
|
||||
title: 'Archaeology',
|
||||
content: <ArchaeologyTab />,
|
||||
},
|
||||
{
|
||||
title: 'Museum',
|
||||
content: <MuseumTab />,
|
||||
|
||||
@@ -78,7 +78,7 @@ export const AgeTab = () => {
|
||||
dropdown('frac_max', fracOptions, 'Maximum fraction', editData.date_meth === 'absolute'),
|
||||
],
|
||||
[''],
|
||||
['Chronostrathigraphic age', textField('chron')],
|
||||
['Chronostratigraphic Age', textField('chron')],
|
||||
['Age Comment', bigTextField('age_comm')],
|
||||
]
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ export const EcometricsTab = () => {
|
||||
const { textField } = useDetailContext<LocalityDetailsType>()
|
||||
|
||||
const ecometrics = [
|
||||
['Estimate of annual precipitation (mm)', textField('estimate_precip')],
|
||||
['Estimate of mean annual temperature (°C)', textField('estimate_temp')],
|
||||
['Estimate of net primary productivity (g/m2/yr)', textField('estimate_npp')],
|
||||
['Woody cover percentage', textField('pers_woody_cover')],
|
||||
['Mean hypsodonty', 'Not yet implemented'],
|
||||
['Estimate of annual precipitation (mm)', textField('estimate_precip', 'number')],
|
||||
['Estimate of mean annual temperature (°C)', textField('estimate_temp', 'number')],
|
||||
['Estimate of net primary productivity (g/m2/yr)', textField('estimate_npp', 'number')],
|
||||
['Woody cover percentage', textField('pers_woody_cover', 'number')],
|
||||
]
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,6 +34,7 @@ export const NavBar = () => {
|
||||
children: [
|
||||
{ title: 'Region', url: '/region' },
|
||||
{ title: 'Project', url: '/project' },
|
||||
{ title: 'Person', url: '/person' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetPersonDetailsQuery } from '../../redux/personReducer'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { DetailView, TabType } from '../DetailView/DetailView'
|
||||
import { PersonTab } from './Tabs/PersonTab'
|
||||
|
||||
export const PersonDetails = () => {
|
||||
const { id } = useParams()
|
||||
const { isLoading, isError, data } = useGetPersonDetailsQuery(id!)
|
||||
|
||||
if (isError) return <div>Error loading data</div>
|
||||
if (isLoading || !data) return <CircularProgress />
|
||||
|
||||
const tabs: TabType[] = [
|
||||
{
|
||||
title: 'Person',
|
||||
content: <PersonTab />,
|
||||
},
|
||||
]
|
||||
|
||||
return <DetailView tabs={tabs} data={data} onWrite={() => {}} validator={() => ({ name: '', error: null })} />
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useMemo } from 'react'
|
||||
import { type MRT_ColumnDef } from 'material-react-table'
|
||||
import { useGetAllPersonsQuery } from '../../redux/personReducer'
|
||||
import { Person } from '@/backendTypes'
|
||||
import { TableView } from '../TableView/TableView'
|
||||
|
||||
export const PersonTable = ({ selectorFn }: { selectorFn?: (id: Person) => void }) => {
|
||||
const personQuery = useGetAllPersonsQuery()
|
||||
const columns = useMemo<MRT_ColumnDef<Person>[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'id',
|
||||
accessorKey: 'initials',
|
||||
header: 'Person Id',
|
||||
},
|
||||
{
|
||||
accessorKey: 'first_name',
|
||||
header: 'First name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'surname',
|
||||
header: 'Surname',
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'Email',
|
||||
},
|
||||
{
|
||||
accessorKey: 'organization',
|
||||
header: 'Organisation',
|
||||
},
|
||||
{
|
||||
accessorKey: 'country',
|
||||
header: 'Country',
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
const checkRowRestriction = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView<Person>
|
||||
selectorFn={selectorFn}
|
||||
checkRowRestriction={checkRowRestriction}
|
||||
idFieldName="initials"
|
||||
columns={columns}
|
||||
data={personQuery.data}
|
||||
url="person"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { PersonDetailsType } from '@/backendTypes'
|
||||
import { ArrayFrame } from '../../DetailView/common/tabLayoutHelpers'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
|
||||
export const PersonTab = () => {
|
||||
const { textField } = useDetailContext<PersonDetailsType>()
|
||||
|
||||
const person = [
|
||||
['User ID', textField('user_id', 'number')],
|
||||
['Initials', textField('initials')],
|
||||
['First Name', textField('first_name')],
|
||||
['Surname', textField('surname')],
|
||||
['Email', textField('email')],
|
||||
['Organization', textField('organization')],
|
||||
['Country', textField('country')],
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<ArrayFrame array={person} title="Person" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -5,43 +5,79 @@ import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
export const DietTab = () => {
|
||||
const { dropdown } = useDetailContext<SpeciesDetailsType>()
|
||||
|
||||
const diet1Options = ['', 'animal', 'omnivore', 'plant']
|
||||
const diet1Options = [
|
||||
'',
|
||||
{ display: 'Animal', value: 'a' },
|
||||
{ display: 'Plant', value: 'p' },
|
||||
{ display: 'Omnivore', value: 'o' },
|
||||
]
|
||||
|
||||
const diet2Options = ['', 'carnivore', 'herbivore', 'invert', 'omnivore', 'piscivore']
|
||||
const diet2Options = [
|
||||
'',
|
||||
{ display: 'Carnivore', value: 'carnivore' },
|
||||
{ display: 'Herbivore', value: 'herbivore' },
|
||||
{ display: 'Invertivore', value: 'invert' },
|
||||
{ display: 'Omnivore', value: 'omnivore' },
|
||||
{ display: 'Piscivore', value: 'piscivore' },
|
||||
]
|
||||
|
||||
const diet3Options = [
|
||||
'',
|
||||
'anim_dom',
|
||||
'br/gr',
|
||||
'browse',
|
||||
'cortex',
|
||||
'exudates',
|
||||
'fr_grass',
|
||||
'fr-se-res',
|
||||
'fruit',
|
||||
'gran',
|
||||
'graze',
|
||||
'h_fruit',
|
||||
'hard',
|
||||
'insect_dom',
|
||||
'm/bone',
|
||||
'meat',
|
||||
'meat_only',
|
||||
'mixed',
|
||||
'myrmec',
|
||||
'plant_dom',
|
||||
'roots',
|
||||
'seeds',
|
||||
'soft',
|
||||
{ display: 'Fresh-grass grazers', value: 'fr_grass' },
|
||||
{ display: 'Grazer (>90% grass)', value: 'graze' },
|
||||
{ display: 'Mixed browser/grazer', value: 'br_gr' },
|
||||
{ display: 'Primarily feed on dicot leaves (folivores)', value: 'browse' },
|
||||
{ display: 'Bark (tree bark)', value: 'cortex' },
|
||||
{ display: 'Exudates: Includes gum, resin, nectar, etc.', value: 'exudates' },
|
||||
{ display: 'Combination of fruit, seed, and exudates', value: 'fr-se-res' },
|
||||
{ display: 'Fruits (fruit)', value: 'fruit' },
|
||||
{ display: 'Fruits (h_fruit)', value: 'h_fruit' },
|
||||
{ display: 'Roots', value: 'roots' },
|
||||
{ display: 'Granivore (gran)', value: 'gran' },
|
||||
{ display: 'Seeds (seeds)', value: 'seeds' },
|
||||
{ display: 'Omnivores, plant foods dominant', value: 'plant_dom' },
|
||||
{
|
||||
display:
|
||||
'Omnivores that consume roughly equal proportions of animal and plant foods, with animal food being dominant',
|
||||
value: 'anim_dom',
|
||||
},
|
||||
{ display: 'Omnivores, insects dominant', value: 'insect_dom' },
|
||||
{ display: 'Meat and specialists on bone consumption (e.g., Hyaena)', value: 'm/bone' },
|
||||
{ display: 'Meat predominant, but take other animal, some plant foods', value: 'meat' },
|
||||
{ display: 'Meat specialists, do not eat other foods', value: 'meat_only' },
|
||||
{ display: 'Molluscivores -- specialists', value: 'hard' },
|
||||
{ display: 'Soft and hard invertebrate food', value: 'mixed' },
|
||||
{ display: 'Myrmecophages (ant & termite specialists)', value: 'myrmec' },
|
||||
{ display: 'Soft invertebrate food', value: 'soft' },
|
||||
]
|
||||
|
||||
const relativeFibreContentOptions = ['', 'h', 'l', 'm']
|
||||
const relativeFibreContentOptions = [
|
||||
'',
|
||||
{ display: 'High level of fibre', value: 'h' },
|
||||
{ display: 'Medium level of fibre', value: 'm' },
|
||||
{ display: 'Low level of fibre', value: 'l' },
|
||||
]
|
||||
|
||||
const selectivityOptions = ['', 's', 'u']
|
||||
const selectivityOptions = [
|
||||
'',
|
||||
{ display: 'Feeds selectively', value: 's' },
|
||||
{ display: 'Feeds unselectively', value: 'u' },
|
||||
]
|
||||
|
||||
const digestionOptions = ['', 'fg', 'hg', 'ru']
|
||||
const digestionOptions = [
|
||||
'',
|
||||
{ display: 'Hindgut fermenter', value: 'hg' },
|
||||
{ display: 'Foregut fermenter', value: 'fg' },
|
||||
{ display: 'True ruminant', value: 'ru' },
|
||||
]
|
||||
|
||||
const huntForageOptions = ['', 'ambush', 'dig', 'pounce/p', 'pursuit']
|
||||
const huntForageOptions = [
|
||||
'',
|
||||
{ display: 'Ambush', value: 'ambush' },
|
||||
{ display: 'Dig', value: 'dig' },
|
||||
{ display: 'Pounce/Pursuit', value: 'pounce/p' },
|
||||
{ display: 'Pursuit', value: 'pursuit' },
|
||||
]
|
||||
|
||||
const diet = [
|
||||
['Diet 1', dropdown('diet1', diet1Options, 'Diet 1')],
|
||||
@@ -50,7 +86,7 @@ export const DietTab = () => {
|
||||
['Relative Fibre Content', dropdown('rel_fib', relativeFibreContentOptions, 'Relative Fibre Content')],
|
||||
['Selectivity', dropdown('selectivity', selectivityOptions, 'Selectivity')],
|
||||
['Digestion', dropdown('digestion', digestionOptions, 'Digestion')],
|
||||
['Hunt/Forage', dropdown('hunt_forage', huntForageOptions, 'Hunt/Forage')],
|
||||
['Hunting or foraging mode for carnivores', dropdown('hunt_forage', huntForageOptions, 'Hunt/Forage')],
|
||||
]
|
||||
|
||||
return (
|
||||
|
||||
@@ -51,6 +51,10 @@ export const LocalitySpeciesTab = () => {
|
||||
accessorKey: 'mni',
|
||||
header: 'MNI',
|
||||
},
|
||||
{
|
||||
accessorKey: 'qua',
|
||||
header: 'QUA',
|
||||
},
|
||||
{
|
||||
accessorKey: 'body_mass',
|
||||
header: 'Body Mass (g)',
|
||||
@@ -79,6 +83,18 @@ export const LocalitySpeciesTab = () => {
|
||||
accessorKey: 'mw_cs_blunt',
|
||||
header: 'MW Blunt',
|
||||
},
|
||||
{
|
||||
accessorKey: 'mw_scale_min',
|
||||
header: 'MW Scale Min',
|
||||
},
|
||||
{
|
||||
accessorKey: 'mw_scale_max',
|
||||
header: 'MW Scale Max',
|
||||
},
|
||||
{
|
||||
accessorKey: 'mw_value',
|
||||
header: 'MW Value',
|
||||
},
|
||||
{
|
||||
header: 'MW Score',
|
||||
Cell: () => <Box>not implemented</Box>,
|
||||
|
||||
@@ -5,52 +5,91 @@ import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
export const LocomotionTab = () => {
|
||||
const { dropdown } = useDetailContext<SpeciesDetailsType>()
|
||||
|
||||
const feedingHabitat1Options = ['', 'ae', 'aq', 'sa', 'te']
|
||||
const feedingHabitat1Options = [
|
||||
'',
|
||||
{ display: 'Aerial', value: 'ae' },
|
||||
{ display: 'Aquatic', value: 'aq' },
|
||||
{ display: 'Semi-aquatic', value: 'sa' },
|
||||
{ display: 'Terrestrial', value: 'te' },
|
||||
]
|
||||
|
||||
const feedingHabitat2Options = ['', 'aerial', 'aquatic', 'arb/surf', 'canopy', 'caves', 'surface', 'u_ground']
|
||||
const feedingHabitat2Options = [
|
||||
'',
|
||||
{ display: 'Feeds primarily while in flight', value: 'aerial' },
|
||||
{ display: 'Feeds in the trees, never on ground', value: 'canopy' },
|
||||
{ display: 'Feeds on ground and on trunk, lower branches, or even canopy of trees', value: 'arb/surf' },
|
||||
{ display: 'Caves', value: 'caves' },
|
||||
{ display: 'Feeds only on ground surface', value: 'surface' },
|
||||
{ display: 'Feeds primarily underground', value: 'u_ground' },
|
||||
{ display: 'Feeds primarily while submerged or at water surface', value: 'aquatic' },
|
||||
]
|
||||
|
||||
const shelterHabitat1Options = ['', 'ae', 'aq', 'sa', 'te']
|
||||
const shelterHabitat1Options = [
|
||||
'',
|
||||
{ display: 'Aerial', value: 'ae' },
|
||||
{ display: 'Terrestrial', value: 'te' },
|
||||
{ display: 'Semi-aquatic', value: 'sa' },
|
||||
{ display: 'Aquatic', value: 'aq' },
|
||||
]
|
||||
|
||||
const shelterHabitat2Options = ['', 'aquatic', 'arb/surf', 'canopy', 'caves', 'surface', 'u_ground']
|
||||
|
||||
const locomotion1Options = ['', 'ae', 'aq', 'sa', 'te']
|
||||
const shelterHabitat2Options = [
|
||||
'',
|
||||
{ display: 'Shelters in the trees, never on ground', value: 'canopy' },
|
||||
{ display: 'Shelters on ground or on trunk, lower branches of trees', value: 'arb/surf' },
|
||||
{ display: 'Caves', value: 'caves' },
|
||||
{ display: 'Shelters only on ground surface', value: 'surface' },
|
||||
{ display: 'Shelters primarily underground', value: 'u_ground' },
|
||||
{ display: 'Shelters under or at water surface', value: 'aquatic' },
|
||||
]
|
||||
const locomotion1Options = [
|
||||
'',
|
||||
{ display: 'Aerial', value: 'ae' },
|
||||
{ display: 'Terrestrial', value: 'te' },
|
||||
{ display: 'Semi-aquatic', value: 'sa' },
|
||||
{ display: 'Aquatic', value: 'aq' },
|
||||
]
|
||||
|
||||
const locomotion2Options = [
|
||||
'',
|
||||
'aerial',
|
||||
'arboreal',
|
||||
'fossorial',
|
||||
'npow_flight',
|
||||
'pow_flight',
|
||||
'scansorial',
|
||||
'semi-fossorial',
|
||||
'surficial',
|
||||
{ display: 'Aerial', value: 'aerial' },
|
||||
{ display: 'Active flying: most birds, bats', value: 'pow_flight' },
|
||||
{ display: 'Gliding, soaring', value: 'npow_flight' },
|
||||
{ display: 'Arboreal', value: 'arboreal' },
|
||||
{ display: 'Scansorial', value: 'scansorial' },
|
||||
{ display: 'Surface', value: 'surficial' },
|
||||
{ display: 'Semi-fossorial', value: 'semi-fossorial' },
|
||||
{ display: 'Fossorial', value: 'fossorial' },
|
||||
]
|
||||
|
||||
const locomotion3Options = [
|
||||
'',
|
||||
'arb_above_br',
|
||||
'arb_suspensory',
|
||||
'bipedal_str',
|
||||
'cursorial',
|
||||
'fast_flight',
|
||||
'gen_quad',
|
||||
'glide',
|
||||
'graviportal',
|
||||
'hover',
|
||||
'k_walk',
|
||||
'parachute',
|
||||
'saltatory',
|
||||
's/f_flight',
|
||||
'slow_flight',
|
||||
'soar',
|
||||
'undulate',
|
||||
'sub_curs',
|
||||
'hyper_curs',
|
||||
'swim_parax',
|
||||
{ display: 'Arboreal, above branch', value: 'arb_above_br' },
|
||||
{ display: 'Arboreal, below-branch suspensory', value: 'arb_suspensory' },
|
||||
{ display: 'Bipedal striding', value: 'bipedal_str' },
|
||||
{ display: 'Running specialists', value: 'cursorial' },
|
||||
{ display: 'Fast powered flight (e.g., ducks, free-tailed bats)', value: 'fast_flight' },
|
||||
{ display: 'General, unspecialized quadrupedal', value: 'gen_quad' },
|
||||
{ display: 'Short, controlled nonpowered flight, glide angle < 45deg', value: 'glide' },
|
||||
{ display: 'Graviportal, e.g., elephants', value: 'graviportal' },
|
||||
{ display: 'Powered flight with specializations for hovering', value: 'hover' },
|
||||
{ display: 'Knuckle-walking', value: 'k_walk' },
|
||||
{ display: 'Nonpowered flight, glide angle >45deg', value: 'parachute' },
|
||||
{ display: 'Jumping, hopping, "ricochetal"', value: 'saltatory' },
|
||||
{ display: 'Fly slow or fast', value: 's/f_flight' },
|
||||
{ display: 'Slow powered flight', value: 'slow_flight' },
|
||||
{ display: 'All types of soaring', value: 'soar' },
|
||||
{ display: 'Undulate, e.g., snakes', value: 'undulate' },
|
||||
{ display: 'Sub-cursorial', value: 'sub_curs' },
|
||||
{ display: 'Hyper-cursorial', value: 'hyper_curs' },
|
||||
{ display: 'Swim-paraxial', value: 'swim_parax' },
|
||||
]
|
||||
|
||||
const activityOptions = ['', 'crepuscular', 'diurnal', 'nocturnal']
|
||||
const activityOptions = [
|
||||
'',
|
||||
{ display: 'Diurnal', value: 'd' },
|
||||
{ display: 'Crepuscular', value: 'c' },
|
||||
{ display: 'Nocturnal', value: 'n' },
|
||||
]
|
||||
|
||||
const feedingHabitat = [
|
||||
['Feeding Habitat 1', dropdown('feedinghab1', feedingHabitat1Options, 'Feeding Habitat 1')],
|
||||
|
||||
@@ -5,17 +5,24 @@ import { useDetailContext } from '@components/DetailView/Context/DetailContext'
|
||||
export const SizeTab = () => {
|
||||
const { dropdown, textField } = useDetailContext<SpeciesDetailsType>()
|
||||
|
||||
const snoutVentLengthOptions = ['', '<10cm', '10cm-1m', '1m-2m', '2m-5m', '>5m']
|
||||
const snoutVentLengthOptions = [
|
||||
'',
|
||||
{ display: '< 10cm', value: '<10cm' },
|
||||
{ display: '10cm - 1m', value: '10cm-1m' },
|
||||
{ display: '1m - 2m', value: '1m-2m' },
|
||||
{ display: '2m - 5m', value: '2m-5m' },
|
||||
{ display: '> 5m', value: '>5m' },
|
||||
]
|
||||
|
||||
const sexualDimorphismSizeOptions = ['', 'y', 'n']
|
||||
const sexualDimorphismSizeOptions = ['', { display: 'Yes', value: 'y' }, { display: 'No', value: 'n' }]
|
||||
|
||||
const sexualDimorphismDisplayOptions = ['', 'y', 'n']
|
||||
const sexualDimorphismDisplayOptions = ['', { display: 'Yes', value: 'y' }, { display: 'No', value: 'n' }]
|
||||
|
||||
const populationStructureOptions = ['', 'soc', 'sol']
|
||||
const populationStructureOptions = ['', { display: 'Social', value: 'soc' }, { display: 'Solitary ', value: 'sol' }]
|
||||
|
||||
const size = [
|
||||
['Body Mass (g)', textField('body_mass')],
|
||||
['Brain Mass (g)', textField('brain_mass')],
|
||||
['Body Mass (g)', textField('body_mass', 'number')],
|
||||
['Brain Mass (g)', textField('brain_mass', 'number')],
|
||||
['Snout-Vent Length', dropdown('sv_length', snoutVentLengthOptions, 'Snout-Vent Length')],
|
||||
]
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const SynonymTab = () => {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '1em' }}>
|
||||
<TextField {...register('syn_genus_name', { required: true })} label="Genus" required />
|
||||
<TextField {...register('syn_species_name', { required: true })} label="Species" required />
|
||||
<TextField {...register('syn_comment', { required: true })} label="Comment" required />
|
||||
<TextField {...register('syn_comment')} label="Comment" />
|
||||
</Box>
|
||||
</EditingModal>
|
||||
)
|
||||
|
||||
@@ -7,35 +7,48 @@ export const TeethTab = () => {
|
||||
|
||||
const toothShapeMulticuspidOptions = [
|
||||
{ value: '', display: 'None' },
|
||||
'bil',
|
||||
'bll',
|
||||
'blm',
|
||||
'bun',
|
||||
'bus',
|
||||
'col',
|
||||
'csc',
|
||||
'cso',
|
||||
'csp',
|
||||
'cyl',
|
||||
'dil',
|
||||
'ect',
|
||||
'edt',
|
||||
'lam',
|
||||
'lss',
|
||||
'md1',
|
||||
'mdp',
|
||||
'plo',
|
||||
'qtb',
|
||||
'sel',
|
||||
'tri',
|
||||
'zal',
|
||||
{ value: 'bil', display: 'Bilophodont' },
|
||||
{ value: 'bll', display: 'Bucco-Lingual Single Row Of Cusps' },
|
||||
{ value: 'blm', display: 'Buno-Lamellar' },
|
||||
{ value: 'bun', display: 'Bunodont' },
|
||||
{ value: 'bus', display: 'Bunoselenodont' },
|
||||
{ value: 'col', display: 'Columnar' },
|
||||
{ value: 'csc', display: 'Carnassial Shear + Crushing Postcanines' },
|
||||
{ value: 'cso', display: 'Carnassial Shear W/ Other Postcanine Functions' },
|
||||
{ value: 'csp', display: 'Carnassial Shear Primary' },
|
||||
{ value: 'cyl', display: 'Cylindrical' },
|
||||
{ value: 'dil', display: 'Dilambdodont' },
|
||||
{ value: 'ect', display: 'Ectolophodont (Ectoloph Dominant Edge)' },
|
||||
{ value: 'edt', display: 'Edentulous' },
|
||||
{ value: 'lam', display: 'Lamellar' },
|
||||
{ value: 'lss', display: 'Leaf-Shaped, Serrated' },
|
||||
{ value: 'md1', display: 'Mesio-Distal Single Row Of Cusps' },
|
||||
{ value: 'mdp', display: 'Mesio-Distal Multiple Rows Of Cusps' },
|
||||
{ value: 'plo', display: 'Plagiolophodont (Flat Trilophodonts)' },
|
||||
{ value: 'qtb', display: 'Quadritubercular' },
|
||||
{ value: 'sel', display: 'Selenodont (General)' },
|
||||
{ value: 'tri', display: 'Simple Tritubercular' },
|
||||
{ value: 'zal', display: 'Zalambdodont' },
|
||||
]
|
||||
|
||||
const hypsodontyOptions = ['', 'bra', 'mes', 'hyp', 'hys', 'trp', 'tpl']
|
||||
const hypsodontyOptions = [
|
||||
'',
|
||||
{ display: 'Brachydont', value: 'bra' },
|
||||
{ display: 'Mesodont', value: 'mes' },
|
||||
{ display: 'Hypsodont', value: 'hyp' },
|
||||
{ display: 'Hypselodont', value: 'hys' },
|
||||
{ display: 'Tooth replacement', value: 'trp' },
|
||||
{ display: 'Tooth plates', value: 'tpl' },
|
||||
]
|
||||
|
||||
const horizodontyOptions = ['', 'bra', 'mes', 'hyp']
|
||||
const horizodontyOptions = [
|
||||
'',
|
||||
{ display: 'Brachyhorizodont', value: 'bra' },
|
||||
{ display: 'Mesohorizodont', value: 'mes' },
|
||||
{ display: 'Hypsohorizodont', value: 'hyp' },
|
||||
]
|
||||
|
||||
const symphysealMobilityOptions = ['', 'y', 'n']
|
||||
const symphysealMobilityOptions = ['', { display: 'No', value: 'n' }, { display: 'Yes', value: 'y' }]
|
||||
|
||||
const cuspShapeOptions = ['', 'R', 'S', 'L']
|
||||
|
||||
@@ -57,19 +70,31 @@ export const TeethTab = () => {
|
||||
|
||||
const coronalCementumOptions = ['', '0', '1']
|
||||
|
||||
const mesowearOptions = ['', 'bil', 'mix', 'att', 'unw']
|
||||
const mesowearOptions = [
|
||||
'',
|
||||
{ display: 'Abrasion-dominated', value: 'bil' },
|
||||
{ display: 'Mixed-dominated', value: 'mix' },
|
||||
{ display: 'Attrition-dominated', value: 'att' },
|
||||
{ display: 'Unworn', value: 'unw' },
|
||||
]
|
||||
|
||||
const microwearOptions = ['', 'pit_dom', 'pit_str', 'str_dom']
|
||||
const microwearOptions = [
|
||||
'',
|
||||
{ display: 'Pits predominant', value: 'pit_dom' },
|
||||
{ display: 'Pits and striae appear equally dominant', value: 'pit_str' },
|
||||
{ display: 'Striations predominant', value: 'str_dom' },
|
||||
]
|
||||
|
||||
const multicuspid = [
|
||||
['Tooth Shape - Multicuspid', dropdown('tshm', toothShapeMulticuspidOptions, 'Tooth Shape - Multicuspid')],
|
||||
['Hypsodonty', dropdown('crowntype', hypsodontyOptions, 'Hypsodonty')],
|
||||
['Horizodonty', dropdown('horizodonty', horizodontyOptions, 'Horizodonty')],
|
||||
['Symphyseal Mobility', dropdown('symph_mob', symphysealMobilityOptions, 'Symphyseal Mobility')],
|
||||
['Relative Blade Length of Lower Carnassial', textField('relative_blade_length')],
|
||||
['Relative Blade Length of Lower Carnassial', textField('relative_blade_length', 'number')],
|
||||
]
|
||||
|
||||
const developmental = [
|
||||
['Developmental Crown Type', 'Not implemented'],
|
||||
['Cusp shape', dropdown('cusp_shape', cuspShapeOptions, 'Cusp shape')],
|
||||
['Buccal cusp count', dropdown('cusp_count_buccal', buccalCuspCountOptions, 'Buccal cusp count')],
|
||||
['Lingual cusp count', dropdown('cusp_count_lingual', lingualCuspCountOptions, 'Lingual cusp count')],
|
||||
@@ -78,6 +103,7 @@ export const TeethTab = () => {
|
||||
]
|
||||
|
||||
const functional = [
|
||||
['Functional Crown Type', 'Not implemented'],
|
||||
['Presence of acute lophs (AL)', dropdown('fct_al', acuteLophsOptions, 'Presence of acute lophs (AL)')],
|
||||
[
|
||||
'Presence of obtuse or basin-like lophs (OL)',
|
||||
@@ -94,15 +120,15 @@ export const TeethTab = () => {
|
||||
const mesowear = [
|
||||
['Mesowear'],
|
||||
['Type', dropdown('mesowear', mesowearOptions, 'Type')],
|
||||
['Cusp Relief Low (OR%)', textField('mw_or_low')],
|
||||
['Cusp Relief High (OR%)', textField('mw_or_high')],
|
||||
['Cusp Shape Sharp (CS%)', textField('mw_cs_sharp')],
|
||||
['Cusp Shape Rounded (CS%)', textField('mw_cs_round')],
|
||||
['Cusp Shape Blunt (CS%)', textField('mw_cs_blunt')],
|
||||
['Scale Minimum', textField('mw_scale_min')],
|
||||
['Scale Maximum', textField('mw_scale_max')],
|
||||
['Reported Value', textField('mw_value')],
|
||||
['Normalized score'],
|
||||
['Cusp Relief Low (OR%)', textField('mw_or_low', 'number')],
|
||||
['Cusp Relief High (OR%)', textField('mw_or_high', 'number')],
|
||||
['Cusp Shape Sharp (CS%)', textField('mw_cs_sharp', 'number')],
|
||||
['Cusp Shape Rounded (CS%)', textField('mw_cs_round', 'number')],
|
||||
['Cusp Shape Blunt (CS%)', textField('mw_cs_blunt', 'number')],
|
||||
['Scale Minimum', textField('mw_scale_min', 'number')],
|
||||
['Scale Maximum', textField('mw_scale_max', 'number')],
|
||||
['Reported Value', textField('mw_value', 'number')],
|
||||
['Normalized score', 'Not implemented'],
|
||||
['Microwear'],
|
||||
['Type', dropdown('microwear', microwearOptions, 'Type')],
|
||||
]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { UserDetailsType } from '@/backendTypes'
|
||||
import { ArrayFrame } from '../../DetailView/common/tabLayoutHelpers'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
|
||||
export const UserTab = () => {
|
||||
const { textField } = useDetailContext<UserDetailsType>()
|
||||
|
||||
const user = [
|
||||
['User Name', textField('user_name')],
|
||||
['Password', textField('password')],
|
||||
['Confirm Password', textField('password')],
|
||||
['Last log in', textField('last_login')],
|
||||
['User Group', textField('now_user_group')],
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<ArrayFrame array={user} title="User" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetUserDetailsQuery } from '../../redux/userReducer'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { DetailView, TabType } from '../DetailView/DetailView'
|
||||
import { UserTab } from './Tabs/UserTab'
|
||||
|
||||
export const UserDetails = () => {
|
||||
const { id } = useParams()
|
||||
const { isLoading, isError, data } = useGetUserDetailsQuery(id!)
|
||||
|
||||
if (isError) return <div>Error loading data</div>
|
||||
if (isLoading || !data) return <CircularProgress />
|
||||
|
||||
const tabs: TabType[] = [
|
||||
{
|
||||
title: 'Person',
|
||||
content: <UserTab />,
|
||||
},
|
||||
]
|
||||
|
||||
return <DetailView tabs={tabs} data={data} onWrite={() => {}} validator={() => ({ name: '', error: null })} />
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react'
|
||||
import { type MRT_ColumnDef } from 'material-react-table'
|
||||
import { useGetAllUsersQuery } from '../../redux/userReducer'
|
||||
import { PersonDetailsType } from '@/backendTypes'
|
||||
import { TableView } from '../TableView/TableView'
|
||||
|
||||
export const UserTable = ({ selectorFn }: { selectorFn?: (person: PersonDetailsType) => void }) => {
|
||||
const userQuery = useGetAllUsersQuery()
|
||||
const columns = useMemo<MRT_ColumnDef<PersonDetailsType>[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'id',
|
||||
accessorKey: 'User_id',
|
||||
header: 'Id',
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
accessorKey: 'user_name',
|
||||
header: 'User name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'last_login',
|
||||
header: 'Last login',
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
const checkRowRestriction = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView<PersonDetailsType>
|
||||
selectorFn={selectorFn}
|
||||
checkRowRestriction={checkRowRestriction}
|
||||
idFieldName="initials"
|
||||
columns={columns}
|
||||
data={userQuery.data}
|
||||
url="user"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
SpeciesDetailsType,
|
||||
LocalityDetailsType,
|
||||
TimeBoundDetailsType,
|
||||
PersonDetailsType,
|
||||
ProjectDetailsType,
|
||||
RegionDetails as RegionDetailsType,
|
||||
UserDetailsType,
|
||||
} from '@/backendTypes'
|
||||
import { LocalityDetails } from './Locality/LocalityDetails'
|
||||
import { LocalityTable } from './Locality/LocalityTable'
|
||||
@@ -18,10 +20,14 @@ import { TimeBoundTable } from './TimeBound/TimeBoundTable'
|
||||
import { TimeUnitTable } from './TimeUnit/TimeUnitTable'
|
||||
import { TimeUnitDetails } from './TimeUnit/TimeUnitDetails'
|
||||
import { ReferenceDetails } from './Reference/ReferenceDetails'
|
||||
import { PersonTable } from './Person/PersonTable'
|
||||
import { PersonDetails } from './Person/PersonDetails'
|
||||
import { ProjectTable } from './Project/ProjectTable'
|
||||
import { ProjectDetails } from './Project/ProjectDetails'
|
||||
import { RegionTable } from './Region/RegionTable'
|
||||
import { RegionDetails } from './Region/RegionDetails'
|
||||
import { UserTable } from './User/UserTable'
|
||||
import { UserDetails } from './User/UserDetails'
|
||||
|
||||
export const timeBoundPage = (
|
||||
<Page
|
||||
@@ -73,6 +79,16 @@ export const localityPage = (
|
||||
/>
|
||||
)
|
||||
|
||||
export const personPage = (
|
||||
<Page
|
||||
tableView={<PersonTable />}
|
||||
detailView={<PersonDetails />}
|
||||
viewName="person"
|
||||
idFieldName="initials"
|
||||
createTitle={(person: PersonDetailsType) => `${person.surname}`}
|
||||
/>
|
||||
)
|
||||
|
||||
export const projectPage = (
|
||||
<Page
|
||||
tableView={<ProjectTable />}
|
||||
@@ -92,3 +108,13 @@ export const regionPage = (
|
||||
createTitle={(region: RegionDetailsType) => `${region.region}`}
|
||||
/>
|
||||
)
|
||||
|
||||
export const userPage = (
|
||||
<Page
|
||||
tableView={<UserTable />}
|
||||
detailView={<UserDetails />}
|
||||
viewName="user"
|
||||
idFieldName="id"
|
||||
createTitle={(user: UserDetailsType) => `${user.user_name}`}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { api } from './api'
|
||||
import { PersonDetailsType } from '@/backendTypes'
|
||||
|
||||
const personsApi = api.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
getAllPersons: builder.query<PersonDetailsType[], void>({
|
||||
query: () => ({
|
||||
url: `/person/all`,
|
||||
}),
|
||||
}),
|
||||
getPersonDetails: builder.query<PersonDetailsType, string>({
|
||||
query: id => ({
|
||||
url: `/person/${id}`,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetAllPersonsQuery, useGetPersonDetailsQuery } = personsApi
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { api } from '@/redux/api'
|
||||
import { UserFields } from '@shared/types'
|
||||
import { PersonDetailsType } from '@/backendTypes'
|
||||
|
||||
type LoginData = { username: string; password: string }
|
||||
|
||||
@@ -16,6 +17,16 @@ const userApi = api.injectEndpoints({
|
||||
method: 'POST',
|
||||
}),
|
||||
}),
|
||||
getAllUsers: builder.query<PersonDetailsType[], void>({
|
||||
query: () => ({
|
||||
url: `/user/all`,
|
||||
}),
|
||||
}),
|
||||
getUserDetails: builder.query<PersonDetailsType, string>({
|
||||
query: id => ({
|
||||
url: `/user/${id}`,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -46,4 +57,4 @@ const userSlice = createSlice({
|
||||
|
||||
export const { setUser, clearUser } = userSlice.actions
|
||||
export const userReducer = userSlice.reducer
|
||||
export const { useTryLoginMutation } = userApi
|
||||
export const { useTryLoginMutation, useGetAllUsersQuery, useGetUserDetailsQuery } = userApi
|
||||
|
||||
Reference in New Issue
Block a user