Skip to content

Commit 75f0c83

Browse files
authored
Merge pull request #103 from jamaljsr/sorting
add sorting of the channel and history lists
2 parents bc38270 + 6d5c047 commit 75f0c83

File tree

16 files changed

+405
-38
lines changed

16 files changed

+405
-38
lines changed

app/src/__tests__/components/history/HistoryPage.spec.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import HistoryPage from 'components/history/HistoryPage';
88
describe('HistoryPage', () => {
99
let store: Store;
1010

11-
beforeEach(() => {
11+
beforeEach(async () => {
1212
store = createStore();
13+
await store.swapStore.fetchSwaps();
1314
});
1415

1516
const render = () => {
@@ -35,9 +36,35 @@ describe('HistoryPage', () => {
3536
expect(getByText('Updated')).toBeInTheDocument();
3637
});
3738

38-
it('should export channels', () => {
39+
it('should export loop history', () => {
3940
const { getByText } = render();
4041
fireEvent.click(getByText('download.svg'));
4142
expect(saveAs).toBeCalledWith(expect.any(Blob), 'swaps.csv');
4243
});
44+
45+
it('should sort the history list', () => {
46+
const { getByText, store } = render();
47+
expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
48+
expect(store.settingsStore.historySort.descending).toBe(true);
49+
50+
fireEvent.click(getByText('Status'));
51+
expect(store.settingsStore.historySort.field).toBe('stateLabel');
52+
53+
fireEvent.click(getByText('Type'));
54+
expect(store.settingsStore.historySort.field).toBe('typeName');
55+
56+
fireEvent.click(getByText('Amount'));
57+
expect(store.settingsStore.historySort.field).toBe('amount');
58+
59+
fireEvent.click(getByText('Created'));
60+
expect(store.settingsStore.historySort.field).toBe('initiationTime');
61+
62+
fireEvent.click(getByText('Updated'));
63+
expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
64+
expect(store.settingsStore.historySort.descending).toBe(false);
65+
66+
fireEvent.click(getByText('Updated'));
67+
expect(store.settingsStore.historySort.field).toBe('lastUpdateTime');
68+
expect(store.settingsStore.historySort.descending).toBe(true);
69+
});
4370
});

app/src/__tests__/components/loop/LoopPage.spec.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,40 @@ describe('LoopPage component', () => {
173173
expect(getByText('Review Loop amount and fee')).toBeInTheDocument();
174174
expect(store.buildSwapStore.processingTimeout).toBeUndefined();
175175
});
176+
177+
it('should sort the channel list', () => {
178+
const { getByText, store } = render();
179+
expect(getByText('Capacity')).toBeInTheDocument();
180+
expect(store.settingsStore.channelSort.field).toBeUndefined();
181+
expect(store.settingsStore.channelSort.descending).toBe(true);
182+
183+
fireEvent.click(getByText('Can Receive'));
184+
expect(store.settingsStore.channelSort.field).toBe('remoteBalance');
185+
186+
fireEvent.click(getByText('Can Send'));
187+
expect(store.settingsStore.channelSort.field).toBe('localBalance');
188+
189+
fireEvent.click(getByText('In Fee %'));
190+
expect(store.settingsStore.channelSort.field).toBe('remoteFeeRate');
191+
192+
fireEvent.click(getByText('Uptime %'));
193+
expect(store.settingsStore.channelSort.field).toBe('uptimePercent');
194+
195+
fireEvent.click(getByText('Peer/Alias'));
196+
expect(store.settingsStore.channelSort.field).toBe('aliasLabel');
197+
198+
fireEvent.click(getByText('Capacity'));
199+
expect(store.settingsStore.channelSort.field).toBe('capacity');
200+
expect(store.settingsStore.channelSort.descending).toBe(false);
201+
202+
fireEvent.click(getByText('Capacity'));
203+
expect(store.settingsStore.channelSort.field).toBe('capacity');
204+
expect(store.settingsStore.channelSort.descending).toBe(true);
205+
206+
expect(getByText('slash.svg')).toBeInTheDocument();
207+
fireEvent.click(getByText('slash.svg'));
208+
expect(store.settingsStore.channelSort.field).toBeUndefined();
209+
expect(store.settingsStore.channelSort.descending).toBe(true);
210+
});
176211
});
177212
});

app/src/assets/icons/arrow-down.svg

Lines changed: 4 additions & 0 deletions
Loading

app/src/assets/icons/arrow-up.svg

Lines changed: 4 additions & 0 deletions
Loading

app/src/assets/icons/slash.svg

Lines changed: 4 additions & 0 deletions
Loading

app/src/components/base/icons.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { ReactComponent as ArrowDownIcon } from 'assets/icons/arrow-down.svg';
12
import { ReactComponent as ArrowLeftIcon } from 'assets/icons/arrow-left.svg';
23
import { ReactComponent as ArrowRightIcon } from 'assets/icons/arrow-right.svg';
4+
import { ReactComponent as ArrowUpIcon } from 'assets/icons/arrow-up.svg';
35
import { ReactComponent as BitcoinIcon } from 'assets/icons/bitcoin.svg';
46
import { ReactComponent as BoltIcon } from 'assets/icons/bolt.svg';
57
import { ReactComponent as CheckIcon } from 'assets/icons/check.svg';
@@ -16,10 +18,11 @@ import { ReactComponent as MaximizeIcon } from 'assets/icons/maximize.svg';
1618
import { ReactComponent as MenuIcon } from 'assets/icons/menu.svg';
1719
import { ReactComponent as MinimizeIcon } from 'assets/icons/minimize.svg';
1820
import { ReactComponent as RefreshIcon } from 'assets/icons/refresh-cw.svg';
21+
import { ReactComponent as CancelIcon } from 'assets/icons/slash.svg';
1922
import { styled } from 'components/theme';
2023

2124
interface IconProps {
22-
size?: 'small' | 'medium' | 'large';
25+
size?: 'x-small' | 'small' | 'medium' | 'large';
2326
onClick?: () => void;
2427
}
2528

@@ -39,6 +42,13 @@ const Icon = styled.span<IconProps>`
3942
}
4043
`}
4144
45+
${props =>
46+
props.size === 'x-small' &&
47+
`
48+
width: 16px;
49+
height: 16px;
50+
`}
51+
4252
${props =>
4353
props.size === 'small' &&
4454
`
@@ -61,10 +71,13 @@ const Icon = styled.span<IconProps>`
6171
`}
6272
`;
6373

74+
export const ArrowLeft = Icon.withComponent(ArrowLeftIcon);
6475
export const ArrowRight = Icon.withComponent(ArrowRightIcon);
76+
export const ArrowUp = Icon.withComponent(ArrowUpIcon);
77+
export const ArrowDown = Icon.withComponent(ArrowDownIcon);
78+
export const Cancel = Icon.withComponent(CancelIcon);
6579
export const Clock = Icon.withComponent(ClockIcon);
6680
export const Download = Icon.withComponent(DownloadIcon);
67-
export const ArrowLeft = Icon.withComponent(ArrowLeftIcon);
6881
export const Bolt = Icon.withComponent(BoltIcon);
6982
export const Bitcoin = Icon.withComponent(BitcoinIcon);
7083
export const Check = Icon.withComponent(CheckIcon);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React, { useCallback } from 'react';
2+
import { SortParams } from 'types/state';
3+
import { ArrowDown, ArrowUp, HeaderFour } from 'components/base';
4+
import { styled } from 'components/theme';
5+
6+
const Styled = {
7+
HeaderFour: styled(HeaderFour)<{ selected: boolean }>`
8+
${props =>
9+
props.selected &&
10+
`
11+
color: ${props.theme.colors.white};
12+
`}
13+
14+
&:hover {
15+
cursor: pointer;
16+
color: ${props => props.theme.colors.white};
17+
}
18+
`,
19+
Icon: styled.span`
20+
display: inline-block;
21+
margin-left: 6px;
22+
23+
svg {
24+
padding: 0;
25+
}
26+
`,
27+
};
28+
29+
interface Props<T> {
30+
field: keyof T;
31+
sort: SortParams<T>;
32+
onSort: (field: SortParams<T>['field'], descending: boolean) => void;
33+
}
34+
35+
const SortableHeader = <T,>({
36+
field,
37+
sort,
38+
onSort,
39+
children,
40+
}: React.PropsWithChildren<Props<T>>) => {
41+
const selected = field === sort.field;
42+
const SortIcon = sort.descending ? ArrowDown : ArrowUp;
43+
44+
const handleSortClick = useCallback(() => {
45+
const descending = selected ? !sort.descending : false;
46+
onSort(field, descending);
47+
}, [selected, sort.descending, field, onSort]);
48+
49+
const { HeaderFour, Icon } = Styled;
50+
return (
51+
<HeaderFour selected={selected} onClick={handleSortClick}>
52+
{children}
53+
{selected && (
54+
<Icon>
55+
<SortIcon size="x-small" />
56+
</Icon>
57+
)}
58+
</HeaderFour>
59+
);
60+
};
61+
62+
export default SortableHeader;

app/src/components/history/HistoryRow.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, { CSSProperties } from 'react';
22
import { observer } from 'mobx-react-lite';
33
import { usePrefixedTranslation } from 'hooks';
4+
import { useStore } from 'store';
45
import { Swap } from 'store/models';
5-
import { Column, HeaderFour, Row } from 'components/base';
6+
import { Column, Row } from 'components/base';
7+
import SortableHeader from 'components/common/SortableHeader';
68
import Unit from 'components/common/Unit';
79
import SwapDot from 'components/loop/SwapDot';
810
import { styled } from 'components/theme';
@@ -43,31 +45,65 @@ const Styled = {
4345
`,
4446
};
4547

46-
export const HistoryRowHeader: React.FC = () => {
48+
const RowHeader: React.FC = () => {
4749
const { l } = usePrefixedTranslation('cmps.history.HistoryRowHeader');
50+
const { settingsStore } = useStore();
51+
4852
const { HeaderRow, ActionColumn, HeaderColumn } = Styled;
4953
return (
5054
<HeaderRow>
5155
<ActionColumn />
5256
<HeaderColumn cols={3}>
53-
<HeaderFour>{l('status')}</HeaderFour>
57+
<SortableHeader<Swap>
58+
field="stateLabel"
59+
sort={settingsStore.historySort}
60+
onSort={settingsStore.setHistorySort}
61+
>
62+
{l('status')}
63+
</SortableHeader>
5464
</HeaderColumn>
5565
<HeaderColumn>
56-
<HeaderFour>{l('type')}</HeaderFour>
66+
<SortableHeader<Swap>
67+
field="typeName"
68+
sort={settingsStore.historySort}
69+
onSort={settingsStore.setHistorySort}
70+
>
71+
{l('type')}
72+
</SortableHeader>
5773
</HeaderColumn>
5874
<HeaderColumn right>
59-
<HeaderFour>{l('amount')}</HeaderFour>
75+
<SortableHeader<Swap>
76+
field="amount"
77+
sort={settingsStore.historySort}
78+
onSort={settingsStore.setHistorySort}
79+
>
80+
{l('amount')}
81+
</SortableHeader>
6082
</HeaderColumn>
6183
<HeaderColumn right>
62-
<HeaderFour>{l('created')}</HeaderFour>
84+
<SortableHeader<Swap>
85+
field="initiationTime"
86+
sort={settingsStore.historySort}
87+
onSort={settingsStore.setHistorySort}
88+
>
89+
{l('created')}
90+
</SortableHeader>
6391
</HeaderColumn>
6492
<HeaderColumn right>
65-
<HeaderFour>{l('updated')}</HeaderFour>
93+
<SortableHeader<Swap>
94+
field="lastUpdateTime"
95+
sort={settingsStore.historySort}
96+
onSort={settingsStore.setHistorySort}
97+
>
98+
{l('updated')}
99+
</SortableHeader>
66100
</HeaderColumn>
67101
</HeaderRow>
68102
);
69103
};
70104

105+
export const HistoryRowHeader = observer(RowHeader);
106+
71107
interface Props {
72108
swap: Swap;
73109
style?: CSSProperties;

0 commit comments

Comments
 (0)