Skip to content

Commit

Permalink
Cleanup datatable (#4091)
Browse files Browse the repository at this point in the history
* cleanup datatable

* sort filtered items by `sortValue || value`
  • Loading branch information
TheGostKasper authored and AsmaaNabilBakr committed Nov 6, 2023
1 parent ae13179 commit 182e873
Show file tree
Hide file tree
Showing 11 changed files with 432 additions and 277 deletions.
249 changes: 50 additions & 199 deletions ui/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
import {
Checkbox,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@material-ui/core";
import _ from "lodash";
import qs from "query-string";
import * as React from "react";
Expand All @@ -24,31 +15,25 @@ import FilterDialog, {
} from "../FilterDialog";
import Flex from "../Flex";
import Icon, { IconType } from "../Icon";
import InfoModal from "../InfoModal";
import SearchField from "../SearchField";
import Spacer from "../Spacer";
import Text from "../Text";
import {
filterRows,
filterSelectionsToQueryString,
filterText,
initialFormState,
parseFilterStateFromURL,
sortByField,
toPairs,
} from "./helpers";
import SortableLabel from "./SortableLabel";

import SearchedNamespacesModal from "./TableView/SearchedNamespacesModal";
import TableView from "./TableView/TableView";
import { SortField } from "./TableView/types";
import { Field, FilterState } from "./types";

/** DataTable Properties */
export interface Props {
/** The ID of the table. */
id?: string;
/** CSS MUI Overrides or other styling. */
className?: string;
/** A list of objects with four fields: `label`, which is a string representing the column header, `value`, which can be a string, or a function that extracts the data needed to fill the table cell, and `sortValue`, which customizes your input to the search function */
fields: Field[];
/** A list of data that will be iterated through to create the columns described in `fields`. */
rows?: any[];
filters?: FilterConfig;
dialogOpen?: boolean;
Expand All @@ -59,12 +44,6 @@ export interface Props {
disableSort?: boolean;
searchedNamespaces?: SearchedNamespaces;
}
//styled components
const EmptyRow = styled(TableRow)<{ colSpan: number }>`
td {
text-align: center;
}
`;

const TopBar = styled(Flex)`
max-width: 100%;
Expand All @@ -75,7 +54,6 @@ const IconFlex = styled(Flex)`
padding: 0 ${(props) => props.theme.spacing.small};
`;

/** Form DataTable */
function UnstyledDataTable({
id,
className,
Expand All @@ -90,21 +68,31 @@ function UnstyledDataTable({
disableSort,
searchedNamespaces,
}: Props) {
//URL info
const history = useHistory();
const location = useLocation();
const search = location.search;
const state = parseFilterStateFromURL(search);

const [filterDialogOpen, setFilterDialogOpen] = React.useState(dialogOpen);
const [searchedNamespacesModalOpen, setSearchedNamespacesModalOpen] =
React.useState(false);

const [checked, setChecked] = React.useState([]);

const [filterState, setFilterState] = React.useState<FilterState>({
filters: selectionsToFilters(state.initialSelections, filters),
formState: initialFormState(filters, state.initialSelections),
textFilters: state.textFilters,
});

const [sortedItem, setSortedItem] = React.useState<SortField | null>(() => {
const defaultSortField = fields.find((f) => f.defaultSort);
const sortField = defaultSortField
? {
...defaultSortField,
reverseSort: false,
}
: null;
return sortField;
});

const handleFilterChange = (sel: FilterSelections) => {
const filterQuery = filterSelectionsToQueryString(sel);
history.replace({ ...location, search: filterQuery });
Expand All @@ -114,6 +102,20 @@ function UnstyledDataTable({
filtered = filterText(filtered, fields, filterState.textFilters);
const chips = toPairs(filterState);

const sortItems = (filtered) => {
let sorted = filtered;
if (sortedItem) {
sorted = _.orderBy(
filtered,
[sortedItem.sortValue || sortedItem.value],
[sortedItem.reverseSort ? "desc" : "asc"]
);
}
return sorted;
};

const items = sortItems(filtered);

const doChange = (formState) => {
if (handleFilterChange) {
handleFilterChange(formState);
Expand Down Expand Up @@ -176,95 +178,8 @@ function UnstyledDataTable({
setFilterState({ ...filterState, filters, formState });
};

const [sortFieldIndex, setSortFieldIndex] = React.useState(() => {
let sortFieldIndex = fields.findIndex((f) => f.defaultSort);

if (sortFieldIndex === -1) {
sortFieldIndex = 0;
}

return sortFieldIndex;
});

const secondarySortFieldIndex = fields.findIndex((f) => f.secondarySort);

const [reverseSort, setReverseSort] = React.useState(false);

let sortFields = [fields[sortFieldIndex]];

const useSecondarySort =
secondarySortFieldIndex > -1 && sortFieldIndex != secondarySortFieldIndex;

if (useSecondarySort) {
sortFields = sortFields.concat(fields[secondarySortFieldIndex]);
sortFields = sortFields.concat(
fields.filter(
(_, index) =>
index != sortFieldIndex && index != secondarySortFieldIndex
)
);
} else {
sortFields = sortFields.concat(
fields.filter((_, index) => index != sortFieldIndex)
);
}

const sorted = sortByField(
filtered,
reverseSort,
sortFields,
useSecondarySort,
disableSort
);

const numFields = fields.length + (checkboxes ? 1 : 0);

const [checked, setChecked] = React.useState([]);

const r = _.map(sorted, (r, i) => {
return (
<TableRow key={r.uid || i}>
{checkboxes && (
<TableCell style={{ padding: "0px" }}>
<Checkbox
checked={_.includes(checked, r.uid)}
onChange={(e) => {
if (e.target.checked) setChecked([...checked, r.uid]);
else setChecked(_.without(checked, r.uid));
}}
color="primary"
/>
</TableCell>
)}
{_.map(fields, (f) => {
const style: React.CSSProperties = {
...(f.minWidth && { minWidth: f.minWidth }),
...(f.maxWidth && { maxWidth: f.maxWidth }),
};

return (
<TableCell
style={Object.keys(style).length > 0 ? style : undefined}
key={f.label}
>
<Text>
{(typeof f.value === "function" ? f.value(r) : r[f.value]) ||
"-"}
</Text>
</TableCell>
);
})}
</TableRow>
);
});

return (
<Flex wide tall column className={className}>
<InfoModal
searchedNamespaces={searchedNamespaces}
open={searchedNamespacesModalOpen}
onCloseModal={setSearchedNamespacesModalOpen}
/>
<TopBar wide align end>
{checkboxes && <CheckboxActions checked={checked} rows={filtered} />}
{filters && !hideSearchAndFilters && (
Expand All @@ -276,18 +191,9 @@ function UnstyledDataTable({
/>
<IconFlex align>
{searchedNamespaces && (
<IconButton
onClick={() =>
setSearchedNamespacesModalOpen(!searchedNamespacesModalOpen)
}
variant="text"
>
<Icon
type={IconType.InfoIcon}
size="medium"
color="neutral20"
/>
</IconButton>
<SearchedNamespacesModal
searchedNamespaces={searchedNamespaces}
/>
)}
<SearchField onSubmit={handleTextSearchSubmit} />
<IconButton
Expand All @@ -306,76 +212,21 @@ function UnstyledDataTable({
)}
</TopBar>
<Flex wide tall>
<TableContainer id={id}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
{checkboxes && (
<TableCell key={"checkboxes"}>
<Checkbox
checked={filtered?.length === checked.length}
onChange={(e) =>
e.target.checked
? setChecked(filtered?.map((r) => r.uid))
: setChecked([])
}
color="primary"
/>
</TableCell>
)}
{_.map(fields, (f, index) => (
<TableCell key={f.label}>
{typeof f.labelRenderer === "function" ? (
f.labelRenderer(r)
) : (
<SortableLabel
fields={fields}
fieldIndex={index}
sortFieldIndex={sortFieldIndex}
reverseSort={reverseSort}
setSortFieldIndex={(...args) => {
if (onColumnHeaderClick) {
onColumnHeaderClick(f);
}

setSortFieldIndex(...args);
}}
setReverseSort={(isReverse) => {
if (onColumnHeaderClick) {
onColumnHeaderClick(f);
}

setReverseSort(isReverse);
}}
/>
)}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{r.length > 0 ? (
r
) : (
<EmptyRow colSpan={numFields}>
<TableCell colSpan={numFields}>
<Flex center align>
<Icon
color="neutral20"
type={IconType.RemoveCircleIcon}
size="base"
/>
<Spacer padding="xxs" />
{emptyMessagePlaceholder || (
<Text color="neutral30">No data</Text>
)}
</Flex>
</TableCell>
</EmptyRow>
)}
</TableBody>
</Table>
</TableContainer>
<TableView
fields={fields}
rows={items}
defaultSortedField={sortedItem}
id={id}
hasCheckboxes={checkboxes}
emptyMessagePlaceholder={emptyMessagePlaceholder}
checkedFields={checked}
disableSort={disableSort}
onBatchCheck={(checked) => setChecked(checked)}
onSortChange={(field) => {
if (onColumnHeaderClick) onColumnHeaderClick(field);
setSortedItem(field);
}}
/>
{!hideSearchAndFilters && (
<FilterDialog
onFilterSelect={handleFilterSelect}
Expand Down
6 changes: 2 additions & 4 deletions ui/components/DataTable/SortableLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as React from "react";
import styled from "styled-components";
import Flex from "../Flex";
import Icon, { IconType } from "../Icon";
import Spacer from "../Spacer";
import { Field } from "./types";

type labelProps = {
Expand All @@ -15,7 +14,7 @@ type labelProps = {
setReverseSort: (b: boolean) => void;
};

const TableButton = styled(Button)`
export const TableButton = styled(Button)`
&.MuiButton-root {
margin: 0;
text-transform: none;
Expand Down Expand Up @@ -44,7 +43,7 @@ export default function SortableLabel({
const sort = fields[sortFieldIndex];

return (
<Flex align start>
<Flex align start gap="4">
<TableButton
color="inherit"
variant="text"
Expand All @@ -57,7 +56,6 @@ export default function SortableLabel({
{field.label}
</h2>
</TableButton>
<Spacer padding="xxs" />
{sort.label === field.label ? (
<Icon
type={IconType.ArrowUpwardIcon}
Expand Down
Loading

0 comments on commit 182e873

Please sign in to comment.