Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MEN-7169 - feat: added multi sorting capabilities to devices view #9

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -346,43 +346,39 @@ exports[`Auditlogs Component renders correctly 1`] = `
>
<div
class="columnHeader"
style="cursor: initial;"
>
Performed by
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Action
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Type
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Changed
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
More details
</div>
<div
class="columnHeader"
class="columnHeader sortable"
>
Time
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon selected false css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon selected fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
viewBox="0 0 24 24"
>
<path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,39 @@ exports[`Auditlogs Component renders correctly 1`] = `
>
<div
class="columnHeader"
style="cursor: initial;"
>
Performed by
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Action
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Type
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
Changed
</div>
<div
class="columnHeader"
style="cursor: initial;"
>
More details
</div>
<div
class="columnHeader"
class="columnHeader sortable"
>
Time
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon selected false css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon selected fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
viewBox="0 0 24 24"
>
<path
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/js/components/auditlogs/auditlogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,14 @@ const autoSelectProps = {
renderOption
};

const locationDefaults = { sort: { direction: SORTING_OPTIONS.desc } };

export const AuditLogs = props => {
const [csvLoading, setCsvLoading] = useState(false);

const [date] = useState(getISOStringBoundaries(new Date()));
const { start: today, end: tonight } = date;

const isInitialized = useRef();
const [locationParams, setLocationParams] = useLocationParams('auditlogs', { today, tonight, defaults: locationDefaults });
const [locationParams, setLocationParams] = useLocationParams('auditlogs', { today, tonight });
const { classes } = useStyles();
const dispatch = useDispatch();
const events = useSelector(getAuditLog);
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/js/components/auditlogs/auditlogslist.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
import React from 'react';
import { Link } from 'react-router-dom';

import { ArrowRightAlt as ArrowRightAltIcon, Sort as SortIcon } from '@mui/icons-material';
import { ArrowRightAlt as ArrowRightAltIcon } from '@mui/icons-material';

import { DEPLOYMENT_ROUTES, SORTING_OPTIONS, canAccess } from '@northern.tech/store/constants';

import DeviceIdentityDisplay from '../common/deviceidentity';
import Loader from '../common/loader';
import Pagination from '../common/pagination';
import SortIcon from '../common/sorticon';
import Time from '../common/time';
import EventDetailsDrawer from './eventdetailsdrawer';

Expand Down Expand Up @@ -150,13 +151,12 @@ export const AuditLogsList = ({
<div className="auditlogs-list-item auditlogs-list-item-header muted">
{auditLogColumns.map((column, index) => (
<div
className="columnHeader"
className={`columnHeader${column.sortable ? ' sortable' : ''}`}
key={`columnHeader-${index}`}
onClick={() => (column.sortable ? onChangeSorting() : null)}
style={column.sortable ? {} : { cursor: 'initial' }}
>
{column.title}
{column.sortable ? <SortIcon className={`sortIcon selected ${(sort.direction === SORTING_OPTIONS.desc).toString()}`} /> : null}
{column.sortable && <SortIcon columnKey="time" sortDown={sort.direction === SORTING_OPTIONS.desc} />}
</div>
))}
<div />
Expand Down
57 changes: 45 additions & 12 deletions frontend/src/js/components/common/detailstable.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import React, { useEffect, useState } from 'react';

// material ui
import { Sort as SortIcon } from '@mui/icons-material';
import { Checkbox, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import { SORTING_OPTIONS } from '@northern.tech/store/constants';
import { SORTING_OPTIONS, SORT_DIRECTIONS, TIMEOUTS } from '@northern.tech/store/constants';

import { useDebounce } from '../../utils/debouncehook';
import SortIcon from './sorticon';

const useStyles = makeStyles()(() => ({
header: {
Expand All @@ -32,16 +34,50 @@ const useStyles = makeStyles()(() => ({
}
}));

const HeaderItem = ({ columnKey, hasMultiSort, extras, renderTitle, sortable, onSort, sortOptions, title }) => {
const { direction, key: sortKey } = sortOptions.find(({ key: sortKey }) => columnKey === sortKey) ?? {};
const [sortState, setSortState] = useState({ disabled: !sortKey, direction });
const [resetDirection] = useState(hasMultiSort ? '' : SORT_DIRECTIONS[0]);

const debouncedSortState = useDebounce(sortState, TIMEOUTS.debounceShort);

useEffect(() => {
if (!onSort) {
return;
}
onSort({ key: columnKey, direction: debouncedSortState.direction, disabled: debouncedSortState.disabled });
}, [columnKey, debouncedSortState.direction, debouncedSortState.disabled, onSort]);

const onSortClick = () => {
if (!sortable) {
return;
}
const nextDirectionIndex = SORT_DIRECTIONS.indexOf(sortState.direction) + 1;
const direction = SORT_DIRECTIONS[nextDirectionIndex] ?? resetDirection;
setSortState({ direction, disabled: !direction });
};

const sortDown = sortKey && direction === SORTING_OPTIONS.desc;

return (
<TableCell className={`columnHeader ${sortable ? '' : 'nonSortable'}`} onClick={onSortClick}>
{renderTitle ? renderTitle(extras) : title}
{sortable && <SortIcon columnKey={sortKey} disabled={sortState.disabled} sortDown={sortDown} />}
</TableCell>
);
};

export const DetailsTable = ({
className = '',
columns,
hasMultiSort = false,
items,
onChangeSorting,
onItemClick,
sort = {},
sort = [],
style = {},
tableRef,
onRowSelected = undefined,
onRowSelected,
selectedRows = []
}) => {
const { classes } = useStyles();
Expand Down Expand Up @@ -69,23 +105,20 @@ export const DetailsTable = ({
<Table className={`margin-bottom ${className}`} style={style} ref={tableRef}>
<TableHead className={classes.header}>
<TableRow>
{onRowSelected !== undefined && (
{!!onRowSelected && (
<TableCell>
<Checkbox indeterminate={false} checked={selectedRows.length === items.length} onChange={onSelectAllClick} />
</TableCell>
)}
{columns.map(({ extras, key, renderTitle, sortable, title }) => (
<TableCell key={key} className={`columnHeader ${sortable ? '' : 'nonSortable'}`} onClick={() => (sortable ? onChangeSorting(key) : null)}>
{renderTitle ? renderTitle(extras) : title}
{sortable && <SortIcon className={`sortIcon ${sort.key === key ? 'selected' : ''} ${(sort.direction === SORTING_OPTIONS.desc).toString()}`} />}
</TableCell>
{columns.map(column => (
<HeaderItem key={column.key} columnKey={column.key} hasMultiSort={hasMultiSort} onSort={onChangeSorting} sortOptions={sort} {...column} />
))}
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<TableRow className={onItemClick ? 'clickable' : ''} hover key={item.id || index}>
{onRowSelected !== undefined && (
{onRowSelected && (
<TableCell>
<Checkbox checked={selectedRows.includes(index)} onChange={() => onRowSelection(index)} />
</TableCell>
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/js/components/common/sorticon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Northern.tech AS
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { useEffect, useRef, useState } from 'react';

// material ui
import { Sort } from '@mui/icons-material';

import { TIMEOUTS } from '@northern.tech/store/commonConstants';

const SortIcon = ({ columnKey, disabled = false, sortDown = false }) => {
const timer = useRef();
const [fadeIcon, setFadeIcon] = useState(true);

useEffect(() => {
if (disabled) {
timer.current = setTimeout(() => setFadeIcon(true), TIMEOUTS.oneSecond);
} else {
timer.current = setTimeout(() => setFadeIcon(false), TIMEOUTS.debounceShort);
}
return () => {
clearTimeout(timer.current);
};
}, [disabled]);

return <Sort className={`sortIcon ${columnKey && !disabled ? 'selected' : ''} ${fadeIcon ? 'fadeOut' : ''} ${sortDown}`} style={{ fontSize: 16 }} />;
};

export default SortIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -381,15 +381,15 @@ exports[`DeviceGroups Component renders correctly 1`] = `
</span>
</div>
<div
class="columnHeader flexbox space-between relative"
class="columnHeader flexbox space-between relative sortable"
>
<div
class="flexbox center-aligned"
>
Device ID
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon true css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
Expand Down Expand Up @@ -425,15 +425,15 @@ exports[`DeviceGroups Component renders correctly 1`] = `
</div>
</div>
<div
class="columnHeader flexbox space-between relative"
class="columnHeader flexbox space-between relative sortable"
>
<div
class="flexbox center-aligned"
>
Device type
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon true css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
Expand All @@ -457,15 +457,15 @@ exports[`DeviceGroups Component renders correctly 1`] = `
</div>
</div>
<div
class="columnHeader flexbox space-between relative"
class="columnHeader flexbox space-between relative sortable"
>
<div
class="flexbox center-aligned"
>
Current software
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon true css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
Expand All @@ -489,15 +489,15 @@ exports[`DeviceGroups Component renders correctly 1`] = `
</div>
</div>
<div
class="columnHeader flexbox space-between relative"
class="columnHeader flexbox space-between relative sortable"
>
<div
class="flexbox center-aligned"
>
Latest activity
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon true css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
Expand All @@ -513,15 +513,15 @@ exports[`DeviceGroups Component renders correctly 1`] = `
/>
</div>
<div
class="columnHeader flexbox space-between relative"
class="columnHeader flexbox space-between relative sortable"
>
<div
class="flexbox center-aligned"
>
Status
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon true css-vhgui7-MuiSvgIcon-root"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium sortIcon fadeOut false css-vhgui7-MuiSvgIcon-root"
data-testid="SortIcon"
focusable="false"
style="font-size: 16px;"
Expand Down
Loading