Skip to content

Commit

Permalink
Add column select dropdown, no defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
dauglyon committed Jun 18, 2024
1 parent 05e03e3 commit 2f44c60
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 92 deletions.
141 changes: 113 additions & 28 deletions src/common/components/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useId, useMemo, useState } from 'react';
import {
createColumnHelper,
ColumnDef,
Expand All @@ -22,9 +22,17 @@ import classes from './Table.module.scss';
import { Button } from './Button';
import { CheckBox } from './CheckBox';
import { Loader } from './Loader';
import { HeatMapRow } from '../api/collectionsApi';
import { Tooltip } from '@mui/material';

import { ColumnMeta, HeatMapRow } from '../api/collectionsApi';
import {
Checkbox,
FormControl,
InputLabel,
ListItemText,
MenuItem,
OutlinedInput,
Select,
Tooltip,
} from '@mui/material';
/*
See also: https://tanstack.com/table/v8/docs/api/core/column-def#meta
This supports passing arbitrary data into the table.
Expand Down Expand Up @@ -362,6 +370,8 @@ export const useTableColumns = ({
accessors[id] = (rowData) => rowData[index];
});

const [columnVisibility, setColumnVisibility] = useState({});

const fieldsOrdered = fields
.filter(({ id }) => !exclude.includes(id))
.sort((a, b) => {
Expand All @@ -378,30 +388,41 @@ export const useTableColumns = ({
}
});

return useMemo(
() => {
const columns = createColumnHelper<unknown[]>();
return fieldsOrdered.map((field) =>
columns.accessor(accessors[field.id], {
header: field.displayName ?? field.id.replace(/_/g, ' ').trim(),
id: field.id,
meta: field.options,
cell:
field.render ||
((cell: CellContext<unknown[], unknown>) => {
const val = cell.getValue();
if (typeof val === 'string') return cell.getValue();
if (typeof val === 'number')
return (cell.getValue() as number).toLocaleString();
return cell.getValue();
}),
})
);
},
// We only want to remake the columns if fieldNames or fieldsOrdered have new values
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(fields), JSON.stringify(fieldsOrdered)]
);
return {
columnVisibility,
setColumnVisibility,
...useMemo(
() => {
const columns = createColumnHelper<unknown[]>();
setColumnVisibility((columnVisibility) => ({
...Object.fromEntries(fieldsOrdered.map((col) => [col.id, true])),
...columnVisibility,
}));
return {
columns: fieldsOrdered,
columnDefs: fieldsOrdered.map((field) =>
columns.accessor(accessors[field.id], {
header: field.displayName ?? field.id.replace(/_/g, ' ').trim(),
id: field.id,
meta: field.options,
cell:
field.render ||
((cell: CellContext<unknown[], unknown>) => {
const val = cell.getValue();
if (typeof val === 'string') return cell.getValue();
if (typeof val === 'number')
return (cell.getValue() as number).toLocaleString();
return cell.getValue();
}),
})
),
};
},
// We only want to remake the columns if fieldNames or fieldsOrdered have new values
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(fields), JSON.stringify(fieldsOrdered)]
),
};
};

/**
Expand All @@ -423,3 +444,67 @@ export const usePageBounds = (
lastRow,
};
};

export const ColumnSelect = ({
columnVisibility,
setColumnVisibility,
columnMeta,
}: {
columnVisibility: { [k: string]: boolean | undefined };
setColumnVisibility: React.Dispatch<
React.SetStateAction<{ [k: string]: boolean | undefined }>
>;
columnMeta: { [k: string]: ColumnMeta } | undefined;
}) => {
const id = useId();
const visible = Object.entries(columnVisibility)
.filter(([id, visible]) => visible)
.map(([id]) => id);
return (
<>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id={id}>Columns</InputLabel>
<Select
labelId={id}
id="demo-multiple-checkbox"
multiple
value={visible}
onChange={(visibleCols) => {
const isViz =
typeof visibleCols.target.value === 'string'
? [visibleCols.target.value]
: visibleCols.target.value;
setColumnVisibility(
Object.fromEntries(
Object.entries(columnVisibility).map(([id, val]) => {
return [id, isViz.includes(id)];
})
)
);
}}
input={<OutlinedInput label="Columns" />}
renderValue={(selected) => {
return selected
.map((id) => columnMeta?.[id]?.display_name || id)
.join(', ');
}}
MenuProps={{
PaperProps: {
style: {
maxHeight: 48 * 4.5 + 8,
width: 250,
},
},
}}
>
{Object.entries(columnVisibility).map(([id, value]) => (
<MenuItem key={id} value={id}>
<Checkbox checked={value} />
<ListItemText primary={columnMeta?.[id]?.display_name || id} />
</MenuItem>
))}
</Select>
</FormControl>
</>
);
};
129 changes: 71 additions & 58 deletions src/features/collections/data_products/GenomeAttribs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getSelection,
} from '../../../common/api/collectionsApi';
import {
ColumnSelect,
Pagination,
Table,
usePageBounds,
Expand Down Expand Up @@ -196,65 +197,67 @@ export const GenomeAttribs: FC<{
data?.fields.findIndex((f) => f.name === '__match__') ?? -1;
const idIndex = data?.fields.findIndex((f) => f.name === 'kbase_id') ?? -1;

const columns = useTableColumns({
fields: data?.fields.map((field) => ({
id: field.name,
displayName: columnMeta?.[field.name]?.display_name ?? field.name,
options: {
textAlign: ['float', 'int'].includes(
columnMeta?.[field.name]?.type ?? ''
)
? 'right'
: 'left',
},
render:
field.name === 'kbase_id'
? (cell) => {
// GTDB IDs are not (yet?) UPAs
if (collection_id === 'GTDB') return cell.getValue();
const upa = (cell.getValue() as string).replace(/_/g, '/');
return (
<Link
to={`https://${process.env.REACT_APP_KBASE_DOMAIN}/legacy/dataview/${upa}`}
target="_blank"
>
{upa}
</Link>
);
}
: // HARDCODED Special rendering for the `classification` column
field.name === 'classification'
? (cell) => {
return (
<Tooltip
title={`${cell.getValue()}`}
placement="top"
arrow
enterDelay={800}
>
<Typography
sx={{
direction: 'rtl',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
const { columnDefs, columnVisibility, setColumnVisibility } = useTableColumns(
{
fields: data?.fields.map((field) => ({
id: field.name,
displayName: columnMeta?.[field.name]?.display_name ?? field.name,
options: {
textAlign: ['float', 'int'].includes(
columnMeta?.[field.name]?.type ?? ''
)
? 'right'
: 'left',
},
render:
field.name === 'kbase_id'
? (cell) => {
// GTDB IDs are not (yet?) UPAs
if (collection_id === 'GTDB') return cell.getValue();
const upa = (cell.getValue() as string).replace(/_/g, '/');
return (
<Link
to={`https://${process.env.REACT_APP_KBASE_DOMAIN}/legacy/dataview/${upa}`}
target="_blank"
>
{cell.getValue() as string}
</Typography>
</Tooltip>
);
}
: undefined,
})),
// HARDCODED the field order parameter and the hidden fields parameter hardcode overrides for which columns will appear and in what order
order: ['kbase_display_name', 'kbase_id', 'genome_size'],
exclude: ['__match__', '__sel__'],
});
{upa}
</Link>
);
}
: // HARDCODED Special rendering for the `classification` column
field.name === 'classification'
? (cell) => {
return (
<Tooltip
title={`${cell.getValue()}`}
placement="top"
arrow
enterDelay={800}
>
<Typography
sx={{
direction: 'rtl',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{cell.getValue() as string}
</Typography>
</Tooltip>
);
}
: undefined,
})),
// HARDCODED the field order parameter and the hidden fields parameter hardcode overrides for which columns will appear and in what order
order: ['kbase_display_name', 'kbase_id', 'genome_size'],
exclude: ['__match__', '__sel__'],
}
);

const table = useReactTable<unknown[]>({
data: data?.table || [],
getRowId: (row) => String(row[idIndex]),
columns: columns,
columns: columnDefs,

getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
Expand All @@ -272,10 +275,13 @@ export const GenomeAttribs: FC<{
enableRowSelection: true,
onRowSelectionChange: setSelection,

onColumnVisibilityChange: setColumnVisibility,

state: {
sorting,
pagination,
rowSelection: selection,
columnVisibility,
},
});

Expand Down Expand Up @@ -340,10 +346,17 @@ export const GenomeAttribs: FC<{
justifyContent="space-between"
alignItems="center"
>
<span>
Showing {formatNumber(firstRow)} - {formatNumber(lastRow)} of{' '}
{formatNumber(count || 0)} genomes
</span>
<Stack direction="row" spacing={2} alignItems="center">
<span>
Showing {formatNumber(firstRow)} - {formatNumber(lastRow)} of{' '}
{formatNumber(count || 0)} samples
</span>
<ColumnSelect
columnMeta={columnMeta}
columnVisibility={columnVisibility}
setColumnVisibility={setColumnVisibility}
/>
</Stack>
<Pagination table={table} maxPage={10000 / pagination.pageSize} />
</Stack>
<Table
Expand Down
Loading

0 comments on commit 2f44c60

Please sign in to comment.