Fix filter

This commit is contained in:
keisuke-umezawa
2024-05-02 15:10:48 +09:00
parent c58150634d
commit f89e028f23
2 changed files with 296 additions and 319 deletions
+291 -1
View File
@@ -18,10 +18,36 @@ import {
TableRow,
TableSortLabel,
TextField,
Collapse,
IconButton,
Menu,
MenuItem,
Box,
useTheme,
} from "@mui/material"
import ListItemIcon from "@mui/material/ListItemIcon"
import { styled } from "@mui/system"
import React from "react"
import Paper from "@mui/material/Paper"
import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions"
import FirstPageIcon from "@mui/icons-material/FirstPage"
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
import LastPageIcon from "@mui/icons-material/LastPage"
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
getFilteredRowModel,
getFacetedUniqueValues,
getFacetedRowModel,
SortingState,
ColumnFiltersState,
useReactTable,
} from "@tanstack/react-table"
type Order = "asc" | "desc"
@@ -455,4 +481,268 @@ const isNumber = (
return typeof rowsPerPage === "number"
}
export { DataGrid, DataGridColumn }
function DataGrid2(props: {
data: Trial[]
columns: ColumnDef<Trial>[]
}): React.ReactElement {
const { data, columns } = props
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [filterMenuAnchorEl, setFilterMenuAnchorEl] =
React.useState<null | HTMLElement>(null)
const table = useReactTable({
data,
columns,
state: {
columnFilters,
sorting,
},
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
//
// debugTable: true,
})
const { pageSize, pageIndex } = table.getState().pagination
React.useEffect(() => {
return () => {
table.resetColumnFilters()
}
}, [])
return (
<Box sx={{ width: "100%" }}>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const order = header.column.getIsSorted()
const filterChoices = header.column.getCanFilter()
? Array.from(
header.column.getFacetedUniqueValues().keys()
).sort()
: []
if (
header.column.getCanFilter() &&
!header.column.getIsFiltered()
) {
header.column.setFilterValue([])
console.log(header.column.getFilterValue())
}
return (
<TableCell key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<TableHeaderCellSpan>
{header.column.getCanSort() ? (
<TableSortLabel
active={order !== false}
direction={order || "asc"}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{order !== null ? (
<HiddenSpan>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</HiddenSpan>
) : null}
</TableSortLabel>
) : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
{header.column.getCanFilter() ? (
<>
<IconButton
size="small"
onClick={(e) => {
setFilterMenuAnchorEl(e.currentTarget)
}}
>
<FilterListIcon fontSize="small" />
</IconButton>
<Menu
anchorEl={filterMenuAnchorEl}
open={filterMenuAnchorEl !== null}
onClose={() => {
setFilterMenuAnchorEl(null)
}}
>
{filterChoices.map((choice) => (
<MenuItem
key={choice}
onClick={() => {
const skippedValues =
header.column.getFilterValue() as string[]
const isSkipped =
skippedValues.includes(choice)
const newSkippedValues = isSkipped
? skippedValues.filter(
(v) => v !== choice
)
: skippedValues.concat(choice)
header.column.setFilterValue(
newSkippedValues
)
}}
>
<ListItemIcon>
{header.column.getFilterValue() !==
undefined ? (
(
header.column.getFilterValue() as string[]
).includes(choice) ? (
<CheckBoxOutlineBlankIcon color="primary" />
) : (
<CheckBoxIcon color="primary" />
)
) : null}
</ListItemIcon>
{choice ?? "(missing value)"}
</MenuItem>
))}
</Menu>
</>
) : null}
</TableHeaderCellSpan>
)}
</TableCell>
)
})}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => {
return (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
)
})}
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 50, 100, { label: "All", value: data.length }]}
component="div"
count={table.getFilteredRowModel().rows.length}
rowsPerPage={pageSize}
page={pageIndex}
slotProps={{
select: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
}}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
onRowsPerPageChange={(e) => {
const size = e.target.value ? Number(e.target.value) : 10
table.setPageSize(size)
}}
ActionsComponent={TablePaginationActions}
/>
</Box>
)
}
const TablePaginationActions = (props: TablePaginationActionsProps) => {
const theme = useTheme()
const { count, page, rowsPerPage, onPageChange } = props
const handleFirstPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, 0)
}
const handleBackButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page - 1)
}
const handleNextButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page + 1)
}
const handleLastPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
}
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
)
}
export { DataGrid, DataGrid2, DataGridColumn }
+5 -318
View File
@@ -1,75 +1,24 @@
import React, { FC } from "react"
import {
IconButton,
Button,
useTheme,
TableSortLabel,
Menu,
MenuItem,
} from "@mui/material"
import { IconButton, Button, useTheme } from "@mui/material"
import LinkIcon from "@mui/icons-material/Link"
import DownloadIcon from "@mui/icons-material/Download"
import LinkIcon from "@mui/icons-material/Link"
import { Button, IconButton, useTheme } from "@mui/material"
import React, { FC } from "react"
import { DataGridColumn, DataGrid, DataGrid2 } from "./DataGrid"
import { Link } from "react-router-dom"
import { StudyDetail, Trial } from "ts/types/optuna"
import { DataGrid, DataGridColumn } from "./DataGrid"
import Box from "@mui/material/Box"
import Table from "@mui/material/Table"
import TableBody from "@mui/material/TableBody"
import TableCell from "@mui/material/TableCell"
import TableContainer from "@mui/material/TableContainer"
import TableHead from "@mui/material/TableHead"
import TableRow from "@mui/material/TableRow"
import TablePagination from "@mui/material/TablePagination"
import Paper from "@mui/material/Paper"
import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions"
import FirstPageIcon from "@mui/icons-material/FirstPage"
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
import LastPageIcon from "@mui/icons-material/LastPage"
import { styled } from "@mui/system"
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank"
import CheckBoxIcon from "@mui/icons-material/CheckBox"
import FilterListIcon from "@mui/icons-material/FilterList"
import ListItemIcon from "@mui/material/ListItemIcon"
import {
ColumnDef,
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
getFilteredRowModel,
getFacetedUniqueValues,
SortingState,
ColumnFiltersState,
useReactTable,
Row,
IdType,
FilterFn,
} from "@tanstack/react-table"
const TableHeaderCellSpan = styled("span")({
display: "inline-flex",
})
const HiddenSpan = styled("span")({
border: 0,
clip: "rect(0 0 0 0)",
height: 1,
margin: -1,
overflow: "hidden",
padding: 0,
position: "absolute",
top: 20,
width: 1,
})
const multiValueFilter: FilterFn<any> = <D extends object>(
row: Row<D>,
columnId: IdType<D>,
@@ -79,266 +28,6 @@ const multiValueFilter: FilterFn<any> = <D extends object>(
return !filterValue.includes(rowValue)
}
const TablePaginationActions = (props: TablePaginationActionsProps) => {
const theme = useTheme()
const { count, page, rowsPerPage, onPageChange } = props
const handleFirstPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, 0)
}
const handleBackButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page - 1)
}
const handleNextButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page + 1)
}
const handleLastPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
}
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
)
}
function BasicTable(props: {
data: Trial[]
columns: ColumnDef<Trial>[]
}): React.ReactElement {
const { data, columns } = props
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [filterMenuAnchorEl, setFilterMenuAnchorEl] =
React.useState<null | HTMLElement>(null)
const table = useReactTable({
data,
columns,
state: {
columnFilters,
sorting,
},
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
//
// debugTable: true,
})
const { pageSize, pageIndex } = table.getState().pagination
React.useEffect(() => {
return () => {
table.resetColumnFilters()
}
}, [])
return (
<Box sx={{ width: "100%" }}>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const order = header.column.getIsSorted()
const filterChoices = header.column.getCanFilter()
? Array.from(
header.column.getFacetedUniqueValues().keys()
).sort()
: []
if (
header.column.getCanFilter() &&
!header.column.getIsFiltered()
) {
header.column.setFilterValue([])
console.log(header.column.getFilterValue())
}
return (
<TableCell key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<TableHeaderCellSpan>
{header.column.getCanSort() ? (
<TableSortLabel
active={order !== false}
direction={order || "asc"}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{order !== null ? (
<HiddenSpan>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</HiddenSpan>
) : null}
</TableSortLabel>
) : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
{header.column.getCanFilter() ? (
<>
<IconButton
size="small"
onClick={(e) => {
setFilterMenuAnchorEl(e.currentTarget)
}}
>
<FilterListIcon fontSize="small" />
</IconButton>
<Menu
anchorEl={filterMenuAnchorEl}
open={filterMenuAnchorEl !== null}
onClose={() => {
setFilterMenuAnchorEl(null)
}}
>
{filterChoices.map((choice) => (
<MenuItem
key={choice}
onClick={() => {
const skippedValues =
header.column.getFilterValue() as string[]
const isSkipped =
skippedValues.includes(choice)
const newSkippedValues = isSkipped
? skippedValues.filter(
(v) => v !== choice
)
: skippedValues.concat(choice)
header.column.setFilterValue(
newSkippedValues
)
}}
>
<ListItemIcon>
{(
header.column.getFilterValue() as string[]
).includes(choice) ? (
<CheckBoxOutlineBlankIcon color="primary" />
) : (
<CheckBoxIcon color="primary" />
)}
</ListItemIcon>
{choice ?? "(missing value)"}
</MenuItem>
))}
</Menu>
</>
) : null}
</TableHeaderCellSpan>
)}
</TableCell>
)
})}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => {
return (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
)
})}
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 50, 100, { label: "All", value: data.length }]}
component="div"
count={table.getFilteredRowModel().rows.length}
rowsPerPage={pageSize}
page={pageIndex}
slotProps={{
select: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
}}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
onRowsPerPageChange={(e) => {
const size = e.target.value ? Number(e.target.value) : 10
table.setPageSize(size)
}}
ActionsComponent={TablePaginationActions}
/>
</Box>
)
}
export const TrialTable: FC<{
studyDetail: StudyDetail | null
initialRowsPerPage?: number
@@ -370,8 +59,7 @@ export const TrialTable: FC<{
header: "State",
footer: (info) => info.column.id,
enableSorting: false,
// enableColumnFilter: true,
enableColumnFilter: false,
enableColumnFilter: true,
filterFn: multiValueFilter,
}),
]
@@ -495,8 +183,7 @@ export const TrialTable: FC<{
header: `Param ${s.name}`,
footer: (info) => info.column.id,
enableSorting: sortable,
// enableColumnFilter: filterChoices !== undefined,
enableColumnFilter: false,
enableColumnFilter: filterChoices !== undefined,
filterFn: multiValueFilter,
}
)
@@ -600,7 +287,7 @@ export const TrialTable: FC<{
>
Download CSV File
</Button>
<BasicTable data={trials} columns={tcolumns} />
<DataGrid2 data={trials} columns={tcolumns} />
</>
)
}