mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Added TimeBound with three Tabs
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Router } from 'express'
|
||||
import { getAllTimeBounds, getTimeBoundDetails } from '../services/timeBound'
|
||||
import { getAllTimeBounds, getTimeBoundDetails, getTimeBoundTimeUnits } from '../services/timeBound'
|
||||
import { fixBigInt } from '../utils/common'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -9,9 +10,15 @@ router.get('/all', async (_req, res) => {
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const id = req.params.id
|
||||
const id = parseInt(req.params.id)
|
||||
const time_bound = await getTimeBoundDetails(id)
|
||||
res.status(200).send(time_bound)
|
||||
})
|
||||
|
||||
router.get('/time-units/:id', async (req, res) => {
|
||||
const id = parseInt(req.params.id)
|
||||
const timeUnits = await getTimeBoundTimeUnits(id)
|
||||
return res.status(200).send(fixBigInt(timeUnits))
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { logger } from '../utils/logger'
|
||||
import { prisma } from '../utils/db'
|
||||
import { EditDataType, TimeBoundDetailsType } from '../../../frontend/src/backendTypes'
|
||||
import Prisma from '@prisma/client'
|
||||
import { validateTimeBound } from '../../../frontend/src/validators/timeBound'
|
||||
|
||||
export const getAllTimeBounds = async () => {
|
||||
const result = await prisma.now_tu_bound.findMany({
|
||||
@@ -14,6 +18,24 @@ export const getAllTimeBounds = async () => {
|
||||
|
||||
export const getTimeBoundDetails = async (id: number) => {
|
||||
// TODO: Check if user has access
|
||||
const result = await prisma.now_tu_bound.findUnique({ where: { bid: id } })
|
||||
|
||||
const result = await prisma.now_tu_bound.findUnique({
|
||||
where: { bid: id },
|
||||
include: {
|
||||
now_bau: {
|
||||
include: {
|
||||
now_br: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export const getTimeBoundTimeUnits = async (id: number) => {
|
||||
// TODO: Check if user has access
|
||||
const result = await prisma.now_time_unit.findMany({
|
||||
where: { OR: [{ up_bnd: id }, { low_bnd: id }] },
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FrontPage } from './components/FrontPage'
|
||||
import { NavBar } from './components/NavBar'
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import { Login } from './components/Login'
|
||||
import { localityPage, referencePage, speciesPage, timeUnitPage } from './components/pages'
|
||||
import { localityPage, referencePage, speciesPage, timeBoundPage, timeUnitPage } from './components/pages'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
@@ -21,6 +21,7 @@ const App = () => {
|
||||
<Route element={speciesPage} path="/species/:id?" />
|
||||
<Route element={referencePage} path="/reference/:id?" />
|
||||
<Route element={timeUnitPage} path="/time-unit/:id?" />
|
||||
<Route element={timeBoundPage} path="/time-bound/:id?" />
|
||||
<Route element={<FrontPage />} path="/" />
|
||||
<Route element={<Login />} path="/login" />
|
||||
<Route element={<div>Page not found.</div>} path="*" />
|
||||
|
||||
Vendored
+11
@@ -88,6 +88,15 @@ export type Reference = {
|
||||
|
||||
export type ReferenceDetailsType = Prisma.ref_ref
|
||||
|
||||
export type TimeBound = {
|
||||
bid: number
|
||||
b_name: string
|
||||
age: number
|
||||
b_comment: string
|
||||
}
|
||||
|
||||
export type TimeBoundDetailsType = Prisma.now_tu_bound
|
||||
|
||||
export type TimeUnit = {
|
||||
low_bound: number
|
||||
up_bound: number
|
||||
@@ -106,4 +115,6 @@ export type TimeUnit = {
|
||||
rank: string
|
||||
}
|
||||
|
||||
export type TimeBoundUpdate = Prisma.now_bau & { now_br: Prisma.now_br }
|
||||
|
||||
export type TimeUnitDetailsType = Prisma.now_time_unit
|
||||
|
||||
@@ -15,7 +15,8 @@ export const NavBar = () => {
|
||||
{ title: 'Locality', url: '/locality' },
|
||||
{ title: 'Species', url: '/species' },
|
||||
{ title: 'Reference', url: '/reference' },
|
||||
{ title: 'Time unit', url: '/time-unit' },
|
||||
{ title: 'Time Unit', url: '/time-unit' },
|
||||
{ title: 'Time Bound', url: '/time-bound' },
|
||||
]
|
||||
|
||||
if (user.username === null) pages.push({ title: 'Login', url: '/login' })
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { TimeBoundDetailsType } from '@/backendTypes'
|
||||
import { ArrayFrame } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
|
||||
export const BoundTab = () => {
|
||||
const { textField } = useDetailContext<TimeBoundDetailsType>()
|
||||
|
||||
const bound = [
|
||||
// TODO Add static Class field
|
||||
['Bound Id', textField('bid')],
|
||||
['Name', textField('b_name')],
|
||||
['Age (Ma)', textField('age')],
|
||||
['Comment', textField('b_comment')],
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<ArrayFrame array={bound} title="Bound" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { TimeUnit, TimeBoundDetailsType } from '@/backendTypes'
|
||||
import { useGetTimeBoundTimeUnitsQuery } from '@/redux/timeBoundReducer'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { TableView } from '../../TableView/TableView'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
|
||||
export const TimeUnitTab = () => {
|
||||
const { data } = useDetailContext<TimeBoundDetailsType>()
|
||||
const { data: timeUnitsData, isError } = useGetTimeBoundTimeUnitsQuery(data.bid)
|
||||
|
||||
if (isError) return 'Error loading Time Units.'
|
||||
if (!timeUnitsData) return <CircularProgress />
|
||||
|
||||
const columns = [
|
||||
{
|
||||
accessorKey: 'tu_name',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'tu_display_name',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'up_bnd',
|
||||
// accessorFn: ({ up_bnd }) => up_bnd === data.bid,
|
||||
header: 'Upper Bound',
|
||||
},
|
||||
{
|
||||
accessorKey: 'low_bnd',
|
||||
// accessorFn: ({ low_bnd }) => low_bnd === data.bid,
|
||||
header: 'Lower Bound',
|
||||
},
|
||||
{
|
||||
accessorKey: 'rank',
|
||||
header: 'Rank',
|
||||
},
|
||||
{
|
||||
accessorKey: 'sequence',
|
||||
header: 'Sequence',
|
||||
},
|
||||
{
|
||||
accessorKey: 'tu_comment',
|
||||
header: 'Comment',
|
||||
},
|
||||
]
|
||||
const checkRowRestriction = (row: TimeUnit) => {
|
||||
return !!0
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView<TimeUnit>
|
||||
checkRowRestriction={checkRowRestriction}
|
||||
idFieldName="tu_name"
|
||||
columns={columns}
|
||||
data={timeUnitsData}
|
||||
url="time-unit"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Editable, TimeBoundDetailsType, TimeBoundUpdate } from '@/backendTypes'
|
||||
import { EditableTable } from '@/components/DetailView/common/EditableTable'
|
||||
import { Grouped } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { Box } from '@mui/material'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
|
||||
export const UpdateTab = () => {
|
||||
const formatDate = (date: Date | null) => {
|
||||
if (!date) return 'No date'
|
||||
return new Date(date).toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
const columns: MRT_ColumnDef<TimeBoundUpdate>[] = [
|
||||
{
|
||||
accessorKey: 'bau_date',
|
||||
header: 'Date',
|
||||
Cell: ({ cell }) => formatDate(cell.getValue() as Date | null),
|
||||
},
|
||||
{
|
||||
accessorKey: 'bau_authorizer',
|
||||
header: 'Editor',
|
||||
},
|
||||
{
|
||||
accessorKey: 'bau_coordinator',
|
||||
header: 'Coordinator',
|
||||
},
|
||||
{
|
||||
accessorKey: 'now_br',
|
||||
header: 'Reference',
|
||||
Cell: () => <Box>not implemented</Box>,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Grouped title="Updates">
|
||||
<EditableTable<Editable<TimeBoundUpdate>, TimeBoundDetailsType> columns={columns} field="now_bau" />
|
||||
</Grouped>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetTimeBoundDetailsQuery } from '../../redux/timeBoundReducer'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { DetailView, TabType } from '../DetailView/DetailView'
|
||||
import { BoundTab } from './Tabs/BoundTab'
|
||||
import { TimeUnitTab } from './Tabs/TimeUnitTab.tsx'
|
||||
import { UpdateTab } from './Tabs/UpdateTab'
|
||||
|
||||
export const TimeBoundDetails = () => {
|
||||
const { id } = useParams()
|
||||
const { isLoading, isError, data } = useGetTimeBoundDetailsQuery(id!)
|
||||
|
||||
if (isError) return <div>Error loading data</div>
|
||||
if (isLoading || !data) return <CircularProgress />
|
||||
|
||||
const tabs: TabType[] = [
|
||||
{
|
||||
title: 'Bound',
|
||||
content: <BoundTab />,
|
||||
},
|
||||
{
|
||||
title: 'Time Units',
|
||||
content: <TimeUnitTab />,
|
||||
},
|
||||
{
|
||||
title: 'Updates',
|
||||
content: <UpdateTab />,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<DetailView
|
||||
tabs={tabs}
|
||||
data={data}
|
||||
onWrite={() => {}}
|
||||
validator={() => ({
|
||||
name: 'unknown',
|
||||
error: null,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useMemo } from 'react'
|
||||
import { type MRT_ColumnDef } from 'material-react-table'
|
||||
import { useGetAllTimeBoundsQuery } from '../../redux/timeBoundReducer'
|
||||
import { TimeBound } from '@/backendTypes'
|
||||
import { TableView } from '../TableView/TableView'
|
||||
|
||||
export const TimeBoundTable = ({ selectorFn }: { selectorFn?: (newTimeBound: TimeBound) => void }) => {
|
||||
const time_boundQuery = useGetAllTimeBoundsQuery()
|
||||
const columns = useMemo<MRT_ColumnDef<TimeBound>[]>(
|
||||
() => [
|
||||
{
|
||||
id: 'bid',
|
||||
accessorKey: 'bid',
|
||||
header: 'Time Bound Id',
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
accessorKey: 'b_name',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
accessorKey: 'age',
|
||||
header: 'Age',
|
||||
},
|
||||
{
|
||||
accessorKey: 'b_comment',
|
||||
header: 'Comment',
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
const checkRowRestriction = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<TableView<TimeBound>
|
||||
selectorFn={selectorFn}
|
||||
checkRowRestriction={checkRowRestriction}
|
||||
idFieldName="bid"
|
||||
columns={columns}
|
||||
data={time_boundQuery.data}
|
||||
url="time-bound"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,30 @@
|
||||
import { TimeUnitDetailsType, ReferenceDetailsType, SpeciesDetailsType, LocalityDetailsType } from '@/backendTypes'
|
||||
import {
|
||||
TimeUnitDetailsType,
|
||||
ReferenceDetailsType,
|
||||
SpeciesDetailsType,
|
||||
LocalityDetailsType,
|
||||
TimeBoundDetailsType,
|
||||
} from '@/backendTypes'
|
||||
import { LocalityDetails } from './Locality/LocalityDetails'
|
||||
import { LocalityTable } from './Locality/LocalityTable'
|
||||
import { 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'
|
||||
|
||||
export const timeBoundPage = (
|
||||
<Page
|
||||
tableView={<TimeBoundTable />}
|
||||
detailView={<TimeBoundDetails />}
|
||||
viewName="time-bound"
|
||||
idFieldName="bid"
|
||||
createTitle={(tb: TimeBoundDetailsType) => `${tb.b_name}`}
|
||||
/>
|
||||
)
|
||||
|
||||
export const timeUnitPage = (
|
||||
<Page
|
||||
tableView={<TimeUnitTable />}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { api } from './api'
|
||||
import { TimeBound, TimeBoundDetailsType, TimeUnit } from '@/backendTypes'
|
||||
|
||||
const timeboundsApi = api.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
getAllTimeBounds: builder.query<TimeBound[], void>({
|
||||
query: () => ({
|
||||
url: `/time-bound/all`,
|
||||
}),
|
||||
}),
|
||||
getTimeBoundDetails: builder.query<TimeBoundDetailsType, string>({
|
||||
query: id => ({
|
||||
url: `/time-bound/${id}`,
|
||||
}),
|
||||
}),
|
||||
getTimeBoundTimeUnits: builder.query<TimeUnit[], string>({
|
||||
query: id => ({
|
||||
url: `/time-bound/time-units/${id}`,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetAllTimeBoundsQuery, useGetTimeBoundDetailsQuery, useGetTimeBoundTimeUnitsQuery } = timeboundsApi
|
||||
@@ -0,0 +1,16 @@
|
||||
import { EditDataType, TimeBoundDetailsType } from '../backendTypes'
|
||||
import { Validators, validator } from './validator'
|
||||
|
||||
export const validateTimeBound = (
|
||||
editData: EditDataType<TimeBoundDetailsType>,
|
||||
fieldName: keyof TimeBoundDetailsType
|
||||
) => {
|
||||
const validators: Validators<Partial<EditDataType<TimeBoundDetailsType>>> = {
|
||||
b_name: {
|
||||
name: 'Locality name',
|
||||
},
|
||||
}
|
||||
|
||||
const returnval = validator<TimeBoundDetailsType>(validators, editData, fieldName)
|
||||
return returnval
|
||||
}
|
||||
Reference in New Issue
Block a user