mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Refactor TableView to use hook; create new Page component, save search id's in list, add TopBar with previous/next buttons on DetailView
This commit is contained in:
+35
-8
@@ -2,13 +2,14 @@ import { Container, Grid } from '@mui/material'
|
||||
import { FrontPage } from './components/FrontPage'
|
||||
import { NavBar } from './components/NavBar'
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import { LocalityTable } from './components/Locality/LocalityTable'
|
||||
import { ReferenceTable } from './components/Reference/ReferenceTable'
|
||||
import { SpeciesTable } from './components/Species/SpeciesTable'
|
||||
import { TimeUnitTable } from './components/TimeUnit/TimeUnitTable'
|
||||
import { Login } from './components/Login'
|
||||
import { LocalityDetails } from './components/Locality/LocalityDetails'
|
||||
import { SpeciesDetails } from './components/Species/SpeciesDetails'
|
||||
import { Page } from './components/Page'
|
||||
import { LocalityTable } from './components/Locality/LocalityTable'
|
||||
import { LocalityDetails } from './components/Locality/LocalityDetails'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
@@ -22,12 +23,38 @@ const App = () => {
|
||||
<Container maxWidth="xl" fixed style={{ marginTop: '2em', marginBottom: '2em' }}>
|
||||
<Grid item>
|
||||
<Routes>
|
||||
<Route element={<LocalityDetails />} path="/locality/:id" />
|
||||
<Route element={<LocalityTable />} path="/locality/" />
|
||||
<Route element={<ReferenceTable />} path="/reference/" />
|
||||
<Route element={<SpeciesDetails />} path="/species/:id" />
|
||||
<Route element={<SpeciesTable />} path="/species/" />
|
||||
<Route element={<TimeUnitTable />} path="/time-unit/" />
|
||||
<Route
|
||||
element={
|
||||
<Page
|
||||
tableView={<LocalityTable />}
|
||||
detailView={<LocalityDetails />}
|
||||
viewName="locality"
|
||||
idFieldName="lid"
|
||||
/>
|
||||
}
|
||||
path="/locality/:id?"
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Page
|
||||
tableView={<SpeciesTable />}
|
||||
detailView={<SpeciesDetails />}
|
||||
viewName="species"
|
||||
idFieldName="species_id"
|
||||
/>
|
||||
}
|
||||
path="/species/:id?"
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Page tableView={<ReferenceTable />} detailView={null} viewName="reference" idFieldName="lid" />
|
||||
}
|
||||
path="/reference/:id?"
|
||||
/>
|
||||
<Route
|
||||
element={<Page tableView={<TimeUnitTable />} detailView={null} viewName="time-unit" idFieldName="" />}
|
||||
path="/time-unit/:id?"
|
||||
/>
|
||||
<Route element={<FrontPage />} path="/" />
|
||||
<Route element={<Login />} path="/login" />
|
||||
<Route element={<div>Page not found.</div>} path="*" />
|
||||
|
||||
@@ -3,11 +3,13 @@ import { Box, Button, Paper, Stack, Tab, Tabs } from '@mui/material'
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
|
||||
import EditIcon from '@mui/icons-material/Edit'
|
||||
import SaveIcon from '@mui/icons-material/Save'
|
||||
import { useEffect, useState, JSX } from 'react'
|
||||
import { useEffect, useState, JSX, useContext } from 'react'
|
||||
import { DetailContextProvider, ModeType } from './Context/DetailContext'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useDetailContext } from './hooks'
|
||||
import { DropdownOption, DropdownSelector, EditableTextField, RadioSelector } from './common/editingComponents'
|
||||
import { TopBar } from './TopBar'
|
||||
import { PageContext } from '../Page'
|
||||
|
||||
export type TabType = {
|
||||
title: string
|
||||
@@ -37,8 +39,9 @@ const WriteButton = <T,>({
|
||||
|
||||
const ReturnButton = () => {
|
||||
const navigate = useNavigate()
|
||||
const { tableUrl } = useContext(PageContext)
|
||||
return (
|
||||
<Button onClick={() => navigate(-1)}>
|
||||
<Button onClick={() => navigate(tableUrl, { relative: 'path' })}>
|
||||
<ArrowBackIcon color="primary" style={{ marginRight: '0.35em' }} />
|
||||
Return to table
|
||||
</Button>
|
||||
@@ -103,16 +106,21 @@ export const DetailView = <T extends object>({
|
||||
return (
|
||||
<Stack rowGap={2}>
|
||||
<DetailContextProvider contextState={initialState}>
|
||||
<Box sx={{ display: 'flex' }} gap={10}>
|
||||
<ReturnButton />
|
||||
<Button
|
||||
onClick={() => setMode(mode === 'edit' ? 'read' : 'edit')}
|
||||
variant={mode === 'edit' ? 'contained' : 'outlined'}
|
||||
style={{ width: '12em' }}
|
||||
>
|
||||
<EditIcon /> {mode === 'read' ? 'Edit' : 'Stop editing'}
|
||||
</Button>
|
||||
{mode === 'edit' && onWrite && <WriteButton onWrite={onWrite} setMode={setMode} />}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignContent: 'center', marginTop: 'auto' }}>
|
||||
<Box sx={{ display: 'flex' }} gap={10}>
|
||||
<ReturnButton />
|
||||
<Button
|
||||
onClick={() => setMode(mode === 'edit' ? 'read' : 'edit')}
|
||||
variant={mode === 'edit' ? 'contained' : 'outlined'}
|
||||
style={{ width: '12em' }}
|
||||
>
|
||||
<EditIcon /> {mode === 'read' ? 'Edit' : 'Stop editing'}
|
||||
</Button>
|
||||
{mode === 'edit' && onWrite && <WriteButton onWrite={onWrite} setMode={setMode} />}
|
||||
</Box>
|
||||
<Box sx={{ marginRight: '3em' }}>
|
||||
<TopBar<T> />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={tab} onChange={(_event, newValue) => setTab(newValue as number)}>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Box, Button } from '@mui/material'
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
|
||||
import { useDetailContext } from './hooks'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useContext } from 'react'
|
||||
import { PageContext } from '../Page'
|
||||
|
||||
export const TopBar = <T,>() => {
|
||||
const { data } = useDetailContext<T>()
|
||||
const navigate = useNavigate()
|
||||
const { idList, idFieldName, viewName } = useContext(PageContext)
|
||||
const idListExists = idList?.length > 0
|
||||
const currentIndex = idList.indexOf((data as { [key: string]: string })[idFieldName as string])
|
||||
const nextIndex = currentIndex + 1
|
||||
const previousIndex = currentIndex - 1
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '22px',
|
||||
gap: '1em',
|
||||
}}
|
||||
>
|
||||
<div> Viewing details of a {viewName}</div>
|
||||
{idListExists && (
|
||||
<div>
|
||||
{previousIndex > 0 && (
|
||||
<Button onClick={() => navigate(`/${viewName}/${idList[previousIndex]}`)}>
|
||||
<ArrowBackIcon />
|
||||
</Button>
|
||||
)}
|
||||
{`${currentIndex} of ${idList.length}`}
|
||||
{nextIndex <= idList.length && (
|
||||
<Button onClick={() => navigate(`/${viewName}/${idList[nextIndex]}`)}>
|
||||
<ArrowForwardIcon />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -19,7 +19,6 @@ export const LocalityDetails = () => {
|
||||
const { id } = useParams()
|
||||
const { isLoading, isError, data } = useGetLocalityDetailsQuery(id!)
|
||||
const [editLocalityRequest] = useEditLocalityMutation()
|
||||
|
||||
if (isError) return <div>Error loading data</div>
|
||||
if (isLoading || !data) return <CircularProgress />
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ReactNode, createContext, useState } from 'react'
|
||||
|
||||
export type PageContextType<T> = {
|
||||
idList: string[]
|
||||
setIdList: (ids: string[]) => void
|
||||
idFieldName: keyof T
|
||||
viewName: string
|
||||
tableUrl: string
|
||||
setTableUrl: (newUrl: string) => void
|
||||
}
|
||||
|
||||
export const PageContext = createContext<PageContextType<{ [key: string | number | symbol]: unknown }>>({
|
||||
idFieldName: null!,
|
||||
idList: [],
|
||||
setIdList: () => {},
|
||||
viewName: null!,
|
||||
tableUrl: '',
|
||||
setTableUrl: () => {},
|
||||
})
|
||||
|
||||
export const PageContextProvider = <T extends object>({
|
||||
children,
|
||||
idFieldName,
|
||||
viewName,
|
||||
}: {
|
||||
children: ReactNode | ReactNode[]
|
||||
idFieldName: keyof T
|
||||
viewName: string
|
||||
}) => {
|
||||
const [idList, setIdList] = useState<string[]>([])
|
||||
const [tableUrl, setTableUrl] = useState<string>(`/${viewName}`)
|
||||
|
||||
return (
|
||||
<PageContext.Provider
|
||||
value={{
|
||||
idList,
|
||||
idFieldName,
|
||||
setIdList: (newIdList: string[]) => setIdList(newIdList),
|
||||
viewName,
|
||||
tableUrl,
|
||||
setTableUrl: (newUrl: string) => setTableUrl(newUrl),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = <T extends object>({
|
||||
tableView,
|
||||
detailView,
|
||||
idFieldName,
|
||||
viewName,
|
||||
}: {
|
||||
tableView: ReactNode
|
||||
detailView: ReactNode
|
||||
idFieldName: keyof T
|
||||
viewName: string
|
||||
}) => {
|
||||
const { id } = useParams()
|
||||
return (
|
||||
<PageContextProvider<T> idFieldName={idFieldName} viewName={viewName}>
|
||||
{id ? detailView : tableView}
|
||||
</PageContextProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
type MRT_ColumnFiltersState,
|
||||
MaterialReactTable,
|
||||
type MRT_ColumnDef,
|
||||
type MRT_RowData,
|
||||
MRT_SortingState,
|
||||
MRT_PaginationState,
|
||||
useMaterialReactTable,
|
||||
MaterialReactTable,
|
||||
} from 'material-react-table'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { renderCustomToolbar, renderCustomToolbarModalVersion } from './helpers'
|
||||
import { ActionComponent } from './ActionComponent'
|
||||
import { PageContext } from '../Page'
|
||||
|
||||
type TableStateInUrl = 'sorting' | 'columnfilters' | 'pagination'
|
||||
|
||||
@@ -39,12 +41,11 @@ export const TableView = <T extends MRT_RowData>({
|
||||
selectorFn?: (id: T) => void
|
||||
url?: string
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>([])
|
||||
const [sorting, setSorting] = useState<MRT_SortingState>([])
|
||||
const [pagination, setPagination] = useState<MRT_PaginationState>(defaultPagination)
|
||||
|
||||
const { setIdList, setTableUrl } = useContext(PageContext)
|
||||
const loadStateFromUrl = (state: TableStateInUrl, defaultState: [] | MRT_PaginationState) => {
|
||||
const searchParams = new URLSearchParams(location.search)
|
||||
const stateFromUrl = searchParams.get(state)
|
||||
@@ -52,6 +53,40 @@ export const TableView = <T extends MRT_RowData>({
|
||||
return JSON.parse(stateFromUrl) as object
|
||||
}
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns: columns,
|
||||
data: data || [],
|
||||
state: {
|
||||
columnFilters,
|
||||
showColumnFilters: true,
|
||||
sorting,
|
||||
pagination,
|
||||
density: 'compact',
|
||||
},
|
||||
initialState: {
|
||||
columnVisibility: { id: false },
|
||||
},
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
renderRowActions: ({ row }) => <ActionComponent {...{ selectorFn, url, checkRowRestriction, row, idFieldName }} />,
|
||||
displayColumnDefOptions: { 'mrt-row-actions': { size: 50, header: '' } },
|
||||
enableRowActions: true,
|
||||
onSortingChange: setSorting,
|
||||
onPaginationChange: setPagination,
|
||||
autoResetPageIndex: false,
|
||||
positionPagination: 'both',
|
||||
paginationDisplayMode: 'pages',
|
||||
enableDensityToggle: false,
|
||||
enableGlobalFilter: false,
|
||||
enableColumnActions: false,
|
||||
enableHiding: true,
|
||||
renderToolbarInternalActions:
|
||||
/*
|
||||
To know what components you can render here if necessary, see the source code:
|
||||
https://github.com/KevinVandy/material-react-table/blob/85b98f9aaa038df48aa1dd35123560abce78ee58/packages/material-react-table/src/components/toolbar/MRT_ToolbarInternalButtons.tsx#L45
|
||||
*/
|
||||
selectorFn ? renderCustomToolbarModalVersion : renderCustomToolbar,
|
||||
})
|
||||
|
||||
// Load state from url only on first render
|
||||
useEffect(() => {
|
||||
if (selectorFn) return
|
||||
@@ -67,49 +102,11 @@ export const TableView = <T extends MRT_RowData>({
|
||||
const columnFilterToUrl = `columnfilters=${JSON.stringify(columnFilters)}`
|
||||
const sortingToUrl = `sorting=${JSON.stringify(sorting)}`
|
||||
const paginationToUrl = `pagination=${JSON.stringify(pagination)}`
|
||||
navigate(`${location.pathname}?&${columnFilterToUrl}&${sortingToUrl}&${paginationToUrl}`, {
|
||||
replace: true,
|
||||
})
|
||||
}, [columnFilters, sorting, pagination, location.pathname, navigate, selectorFn])
|
||||
setTableUrl(`${location.pathname}?&${columnFilterToUrl}&${sortingToUrl}&${paginationToUrl}`)
|
||||
setIdList(table.getPrePaginationRowModel().rows.map(row => row.original[idFieldName] as string))
|
||||
}, [columnFilters, sorting, pagination, location.pathname, selectorFn, setIdList, table, idFieldName, setTableUrl])
|
||||
|
||||
if (!data) return <CircularProgress />
|
||||
|
||||
return (
|
||||
<MaterialReactTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
state={{
|
||||
columnFilters,
|
||||
showColumnFilters: true,
|
||||
sorting,
|
||||
pagination,
|
||||
density: 'compact',
|
||||
}}
|
||||
initialState={{
|
||||
columnVisibility: { id: false },
|
||||
}}
|
||||
onColumnFiltersChange={setColumnFilters}
|
||||
renderRowActions={({ row }) => (
|
||||
<ActionComponent {...{ selectorFn, url, checkRowRestriction, row, idFieldName }} />
|
||||
)}
|
||||
displayColumnDefOptions={{ 'mrt-row-actions': { size: 50, header: '' } }}
|
||||
enableRowActions
|
||||
onSortingChange={setSorting}
|
||||
onPaginationChange={setPagination}
|
||||
autoResetPageIndex={false}
|
||||
positionPagination="both"
|
||||
paginationDisplayMode="pages"
|
||||
enableDensityToggle={false}
|
||||
enableGlobalFilter={false}
|
||||
enableColumnActions={false}
|
||||
enableHiding={true}
|
||||
renderToolbarInternalActions={
|
||||
/*
|
||||
To know what components you can render here if necessary, see the source code:
|
||||
https://github.com/KevinVandy/material-react-table/blob/85b98f9aaa038df48aa1dd35123560abce78ee58/packages/material-react-table/src/components/toolbar/MRT_ToolbarInternalButtons.tsx#L45
|
||||
*/
|
||||
selectorFn ? renderCustomToolbarModalVersion : renderCustomToolbar
|
||||
}
|
||||
/>
|
||||
)
|
||||
return <MaterialReactTable table={table} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user