Skip to content

Commit 15909de

Browse files
Cleanup datatable (#4091)
* cleanup datatable * sort filtered items by `sortValue || value`
1 parent 7093984 commit 15909de

File tree

11 files changed

+432
-277
lines changed

11 files changed

+432
-277
lines changed

ui/components/DataTable/DataTable.tsx

Lines changed: 50 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
import {
2-
Checkbox,
3-
Table,
4-
TableBody,
5-
TableCell,
6-
TableContainer,
7-
TableHead,
8-
TableRow,
9-
} from "@material-ui/core";
101
import _ from "lodash";
112
import qs from "query-string";
123
import * as React from "react";
@@ -24,31 +15,25 @@ import FilterDialog, {
2415
} from "../FilterDialog";
2516
import Flex from "../Flex";
2617
import Icon, { IconType } from "../Icon";
27-
import InfoModal from "../InfoModal";
2818
import SearchField from "../SearchField";
29-
import Spacer from "../Spacer";
30-
import Text from "../Text";
3119
import {
3220
filterRows,
3321
filterSelectionsToQueryString,
3422
filterText,
3523
initialFormState,
3624
parseFilterStateFromURL,
37-
sortByField,
3825
toPairs,
3926
} from "./helpers";
40-
import SortableLabel from "./SortableLabel";
27+
28+
import SearchedNamespacesModal from "./TableView/SearchedNamespacesModal";
29+
import TableView from "./TableView/TableView";
30+
import { SortField } from "./TableView/types";
4131
import { Field, FilterState } from "./types";
4232

43-
/** DataTable Properties */
4433
export interface Props {
45-
/** The ID of the table. */
4634
id?: string;
47-
/** CSS MUI Overrides or other styling. */
4835
className?: string;
49-
/** 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 */
5036
fields: Field[];
51-
/** A list of data that will be iterated through to create the columns described in `fields`. */
5237
rows?: any[];
5338
filters?: FilterConfig;
5439
dialogOpen?: boolean;
@@ -59,12 +44,6 @@ export interface Props {
5944
disableSort?: boolean;
6045
searchedNamespaces?: SearchedNamespaces;
6146
}
62-
//styled components
63-
const EmptyRow = styled(TableRow)<{ colSpan: number }>`
64-
td {
65-
text-align: center;
66-
}
67-
`;
6847

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

78-
/** Form DataTable */
7957
function UnstyledDataTable({
8058
id,
8159
className,
@@ -90,21 +68,31 @@ function UnstyledDataTable({
9068
disableSort,
9169
searchedNamespaces,
9270
}: Props) {
93-
//URL info
9471
const history = useHistory();
9572
const location = useLocation();
9673
const search = location.search;
9774
const state = parseFilterStateFromURL(search);
98-
9975
const [filterDialogOpen, setFilterDialogOpen] = React.useState(dialogOpen);
100-
const [searchedNamespacesModalOpen, setSearchedNamespacesModalOpen] =
101-
React.useState(false);
76+
77+
const [checked, setChecked] = React.useState([]);
78+
10279
const [filterState, setFilterState] = React.useState<FilterState>({
10380
filters: selectionsToFilters(state.initialSelections, filters),
10481
formState: initialFormState(filters, state.initialSelections),
10582
textFilters: state.textFilters,
10683
});
10784

85+
const [sortedItem, setSortedItem] = React.useState<SortField | null>(() => {
86+
const defaultSortField = fields.find((f) => f.defaultSort);
87+
const sortField = defaultSortField
88+
? {
89+
...defaultSortField,
90+
reverseSort: false,
91+
}
92+
: null;
93+
return sortField;
94+
});
95+
10896
const handleFilterChange = (sel: FilterSelections) => {
10997
const filterQuery = filterSelectionsToQueryString(sel);
11098
history.replace({ ...location, search: filterQuery });
@@ -114,6 +102,20 @@ function UnstyledDataTable({
114102
filtered = filterText(filtered, fields, filterState.textFilters);
115103
const chips = toPairs(filterState);
116104

105+
const sortItems = (filtered) => {
106+
let sorted = filtered;
107+
if (sortedItem) {
108+
sorted = _.orderBy(
109+
filtered,
110+
[sortedItem.sortValue || sortedItem.value],
111+
[sortedItem.reverseSort ? "desc" : "asc"]
112+
);
113+
}
114+
return sorted;
115+
};
116+
117+
const items = sortItems(filtered);
118+
117119
const doChange = (formState) => {
118120
if (handleFilterChange) {
119121
handleFilterChange(formState);
@@ -176,95 +178,8 @@ function UnstyledDataTable({
176178
setFilterState({ ...filterState, filters, formState });
177179
};
178180

179-
const [sortFieldIndex, setSortFieldIndex] = React.useState(() => {
180-
let sortFieldIndex = fields.findIndex((f) => f.defaultSort);
181-
182-
if (sortFieldIndex === -1) {
183-
sortFieldIndex = 0;
184-
}
185-
186-
return sortFieldIndex;
187-
});
188-
189-
const secondarySortFieldIndex = fields.findIndex((f) => f.secondarySort);
190-
191-
const [reverseSort, setReverseSort] = React.useState(false);
192-
193-
let sortFields = [fields[sortFieldIndex]];
194-
195-
const useSecondarySort =
196-
secondarySortFieldIndex > -1 && sortFieldIndex != secondarySortFieldIndex;
197-
198-
if (useSecondarySort) {
199-
sortFields = sortFields.concat(fields[secondarySortFieldIndex]);
200-
sortFields = sortFields.concat(
201-
fields.filter(
202-
(_, index) =>
203-
index != sortFieldIndex && index != secondarySortFieldIndex
204-
)
205-
);
206-
} else {
207-
sortFields = sortFields.concat(
208-
fields.filter((_, index) => index != sortFieldIndex)
209-
);
210-
}
211-
212-
const sorted = sortByField(
213-
filtered,
214-
reverseSort,
215-
sortFields,
216-
useSecondarySort,
217-
disableSort
218-
);
219-
220-
const numFields = fields.length + (checkboxes ? 1 : 0);
221-
222-
const [checked, setChecked] = React.useState([]);
223-
224-
const r = _.map(sorted, (r, i) => {
225-
return (
226-
<TableRow key={r.uid || i}>
227-
{checkboxes && (
228-
<TableCell style={{ padding: "0px" }}>
229-
<Checkbox
230-
checked={_.includes(checked, r.uid)}
231-
onChange={(e) => {
232-
if (e.target.checked) setChecked([...checked, r.uid]);
233-
else setChecked(_.without(checked, r.uid));
234-
}}
235-
color="primary"
236-
/>
237-
</TableCell>
238-
)}
239-
{_.map(fields, (f) => {
240-
const style: React.CSSProperties = {
241-
...(f.minWidth && { minWidth: f.minWidth }),
242-
...(f.maxWidth && { maxWidth: f.maxWidth }),
243-
};
244-
245-
return (
246-
<TableCell
247-
style={Object.keys(style).length > 0 ? style : undefined}
248-
key={f.label}
249-
>
250-
<Text>
251-
{(typeof f.value === "function" ? f.value(r) : r[f.value]) ||
252-
"-"}
253-
</Text>
254-
</TableCell>
255-
);
256-
})}
257-
</TableRow>
258-
);
259-
});
260-
261181
return (
262182
<Flex wide tall column className={className}>
263-
<InfoModal
264-
searchedNamespaces={searchedNamespaces}
265-
open={searchedNamespacesModalOpen}
266-
onCloseModal={setSearchedNamespacesModalOpen}
267-
/>
268183
<TopBar wide align end>
269184
{checkboxes && <CheckboxActions checked={checked} rows={filtered} />}
270185
{filters && !hideSearchAndFilters && (
@@ -276,18 +191,9 @@ function UnstyledDataTable({
276191
/>
277192
<IconFlex align>
278193
{searchedNamespaces && (
279-
<IconButton
280-
onClick={() =>
281-
setSearchedNamespacesModalOpen(!searchedNamespacesModalOpen)
282-
}
283-
variant="text"
284-
>
285-
<Icon
286-
type={IconType.InfoIcon}
287-
size="medium"
288-
color="neutral20"
289-
/>
290-
</IconButton>
194+
<SearchedNamespacesModal
195+
searchedNamespaces={searchedNamespaces}
196+
/>
291197
)}
292198
<SearchField onSubmit={handleTextSearchSubmit} />
293199
<IconButton
@@ -306,76 +212,21 @@ function UnstyledDataTable({
306212
)}
307213
</TopBar>
308214
<Flex wide tall>
309-
<TableContainer id={id}>
310-
<Table aria-label="simple table">
311-
<TableHead>
312-
<TableRow>
313-
{checkboxes && (
314-
<TableCell key={"checkboxes"}>
315-
<Checkbox
316-
checked={filtered?.length === checked.length}
317-
onChange={(e) =>
318-
e.target.checked
319-
? setChecked(filtered?.map((r) => r.uid))
320-
: setChecked([])
321-
}
322-
color="primary"
323-
/>
324-
</TableCell>
325-
)}
326-
{_.map(fields, (f, index) => (
327-
<TableCell key={f.label}>
328-
{typeof f.labelRenderer === "function" ? (
329-
f.labelRenderer(r)
330-
) : (
331-
<SortableLabel
332-
fields={fields}
333-
fieldIndex={index}
334-
sortFieldIndex={sortFieldIndex}
335-
reverseSort={reverseSort}
336-
setSortFieldIndex={(...args) => {
337-
if (onColumnHeaderClick) {
338-
onColumnHeaderClick(f);
339-
}
340-
341-
setSortFieldIndex(...args);
342-
}}
343-
setReverseSort={(isReverse) => {
344-
if (onColumnHeaderClick) {
345-
onColumnHeaderClick(f);
346-
}
347-
348-
setReverseSort(isReverse);
349-
}}
350-
/>
351-
)}
352-
</TableCell>
353-
))}
354-
</TableRow>
355-
</TableHead>
356-
<TableBody>
357-
{r.length > 0 ? (
358-
r
359-
) : (
360-
<EmptyRow colSpan={numFields}>
361-
<TableCell colSpan={numFields}>
362-
<Flex center align>
363-
<Icon
364-
color="neutral20"
365-
type={IconType.RemoveCircleIcon}
366-
size="base"
367-
/>
368-
<Spacer padding="xxs" />
369-
{emptyMessagePlaceholder || (
370-
<Text color="neutral30">No data</Text>
371-
)}
372-
</Flex>
373-
</TableCell>
374-
</EmptyRow>
375-
)}
376-
</TableBody>
377-
</Table>
378-
</TableContainer>
215+
<TableView
216+
fields={fields}
217+
rows={items}
218+
defaultSortedField={sortedItem}
219+
id={id}
220+
hasCheckboxes={checkboxes}
221+
emptyMessagePlaceholder={emptyMessagePlaceholder}
222+
checkedFields={checked}
223+
disableSort={disableSort}
224+
onBatchCheck={(checked) => setChecked(checked)}
225+
onSortChange={(field) => {
226+
if (onColumnHeaderClick) onColumnHeaderClick(field);
227+
setSortedItem(field);
228+
}}
229+
/>
379230
{!hideSearchAndFilters && (
380231
<FilterDialog
381232
onFilterSelect={handleFilterSelect}

ui/components/DataTable/SortableLabel.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as React from "react";
33
import styled from "styled-components";
44
import Flex from "../Flex";
55
import Icon, { IconType } from "../Icon";
6-
import Spacer from "../Spacer";
76
import { Field } from "./types";
87

98
type labelProps = {
@@ -15,7 +14,7 @@ type labelProps = {
1514
setReverseSort: (b: boolean) => void;
1615
};
1716

18-
const TableButton = styled(Button)`
17+
export const TableButton = styled(Button)`
1918
&.MuiButton-root {
2019
margin: 0;
2120
text-transform: none;
@@ -44,7 +43,7 @@ export default function SortableLabel({
4443
const sort = fields[sortFieldIndex];
4544

4645
return (
47-
<Flex align start>
46+
<Flex align start gap="4">
4847
<TableButton
4948
color="inherit"
5049
variant="text"
@@ -57,7 +56,6 @@ export default function SortableLabel({
5756
{field.label}
5857
</h2>
5958
</TableButton>
60-
<Spacer padding="xxs" />
6159
{sort.label === field.label ? (
6260
<Icon
6361
type={IconType.ArrowUpwardIcon}

0 commit comments

Comments
 (0)