Added PersonTable, PersonDetails, UserTable, UserDetails

This commit is contained in:
Kari Lintulaakso
2024-06-14 14:56:01 +03:00
parent 7a67633623
commit 8454833497
17 changed files with 307 additions and 1 deletions
+2
View File
@@ -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)
+17
View File
@@ -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
+12
View File
@@ -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 { prisma } 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
+16
View File
@@ -0,0 +1,16 @@
import { where } from 'sequelize'
import { prisma } from '../utils/db'
export const getAllPersons = async () => {
const result = await prisma.com_people.findMany({})
return result
}
export const getPersonDetails = async (id: string) => {
// TODO: Check if user has access
const result = await prisma.com_people.findUnique({
where: { initials: id },
})
return result
}
+11
View File
@@ -26,3 +26,14 @@ export const getAllUsers = async () => {
const result = await prisma.com_users.findMany({})
return result
}
export const getUserDetails = async (id: number) => {
// TODO: Check if user has access
const result = await prisma.com_users.findUnique({
where: {
user_id: id,
},
})
return result
}
+4
View File
@@ -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="*" />
+4
View File
@@ -27,6 +27,8 @@ export type SpeciesLocality = Prisma.now_ls & { now_loc: Prisma.now_loc }
export type LocalityUpdate = Prisma.now_lau & { now_lr: Prisma.now_lr }
export type SpeciesUpdate = Prisma.now_sau & { now_lr: Prisma.now_sr }
export type Museum = Prisma.com_mlist
export type Person = Prisma.com_people
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,3 +144,5 @@ export type TimeUnitDetailsType = EditDataType<
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 = EditDataType<Prisma.com_users>
+1
View File
@@ -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" />
</>
)
}
@@ -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 { User } from '@/backendTypes'
import { TableView } from '../TableView/TableView'
export const UserTable = ({ selectorFn }: { selectorFn?: (id: User) => void }) => {
const userQuery = useGetAllUsersQuery()
const columns = useMemo<MRT_ColumnDef<User>[]>(
() => [
{
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<User>
selectorFn={selectorFn}
checkRowRestriction={checkRowRestriction}
idFieldName="initials"
columns={columns}
data={userQuery.data}
url="user"
/>
)
}
+26
View File
@@ -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}`}
/>
)
+19
View File
@@ -0,0 +1,19 @@
import { api } from './api'
import { Person, PersonDetailsType } from '@/backendTypes'
const personsApi = api.injectEndpoints({
endpoints: builder => ({
getAllPersons: builder.query<Person[], void>({
query: () => ({
url: `/person/all`,
}),
}),
getPersonDetails: builder.query<PersonDetailsType, string>({
query: id => ({
url: `/person/${id}`,
}),
}),
}),
})
export const { useGetAllPersonsQuery, useGetPersonDetailsQuery } = personsApi
+12 -1
View File
@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { api } from '@/redux/api'
import { UserFields } from '@shared/types'
import { User, UserDetailsType } from '@/backendTypes'
type LoginData = { username: string; password: string }
@@ -16,6 +17,16 @@ const userApi = api.injectEndpoints({
method: 'POST',
}),
}),
getAllUsers: builder.query<User[], void>({
query: () => ({
url: `/user/all`,
}),
}),
getUserDetails: builder.query<UserDetailsType, 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