Skip to content

Commit c5fe40a

Browse files
committed
history: add sorting to the history list
1 parent 2e6ae91 commit c5fe40a

File tree

5 files changed

+123
-12
lines changed

5 files changed

+123
-12
lines changed

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

+29-2
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/components/history/HistoryRow.tsx

+43-7
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;

app/src/store/models/swap.ts

+25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { action, computed, observable } from 'mobx';
22
import { now } from 'mobx-utils';
33
import * as LOOP from 'types/generated/loop_pb';
4+
import { SortParams } from 'types/state';
45
import Big from 'big.js';
56
import formatDate from 'date-fns/format';
67
import { CsvColumns } from 'util/csv';
@@ -112,6 +113,30 @@ export default class Swap {
112113
this.state = loopSwap.state;
113114
}
114115

116+
/**
117+
* Compares a specific field of two swaps for sorting
118+
* @param a the first swap to compare
119+
* @param b the second swap to compare
120+
* @param sortBy the field and direction to sort the two swaps by
121+
* @returns a positive number if `a`'s field is greater than `b`'s,
122+
* a negative number if `a`'s field is less than `b`'s, or zero otherwise
123+
*/
124+
static compare(a: Swap, b: Swap, field: SortParams<Swap>['field']): number {
125+
switch (field) {
126+
case 'stateLabel':
127+
return a.stateLabel.toLowerCase() > b.stateLabel.toLowerCase() ? 1 : -1;
128+
case 'typeName':
129+
return a.typeName.toLowerCase() > b.typeName.toLowerCase() ? 1 : -1;
130+
case 'amount':
131+
return +a.amount.sub(b.amount);
132+
case 'initiationTime':
133+
return a.initiationTime - b.initiationTime;
134+
case 'lastUpdateTime':
135+
default:
136+
return a.lastUpdateTime - b.lastUpdateTime;
137+
}
138+
}
139+
115140
/**
116141
* Specifies which properties of this class should be exported to CSV
117142
* @param key must match the name of a property on this class

app/src/store/stores/settingsStore.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { action, autorun, observable, toJS } from 'mobx';
22
import { SortParams } from 'types/state';
33
import { BalanceMode, Unit } from 'util/constants';
44
import { Store } from 'store';
5-
import { Channel } from 'store/models';
5+
import { Channel, Swap } from 'store/models';
66

77
export interface PersistentSettings {
88
sidebarVisible: boolean;
99
unit: Unit;
1010
balanceMode: BalanceMode;
1111
tourAutoShown: boolean;
1212
channelSort: SortParams<Channel>;
13+
historySort: SortParams<Swap>;
1314
}
1415

1516
export default class SettingsStore {
@@ -36,6 +37,12 @@ export default class SettingsStore {
3637
descending: true,
3738
};
3839

40+
/** specifies the sorting field and direction for the channel list */
41+
@observable historySort: SortParams<Swap> = {
42+
field: 'lastUpdateTime',
43+
descending: true,
44+
};
45+
3946
/** the chosen language */
4047
@observable lang = 'en-US';
4148

@@ -98,6 +105,17 @@ export default class SettingsStore {
98105
this._store.log.info('reset channel list sort order', toJS(this.channelSort));
99106
}
100107

108+
/**
109+
* Sets the sort field and direction that the swap history list should use
110+
* @param field the swap field to sort by
111+
* @param descending true of the order should be descending, otherwise false
112+
*/
113+
@action.bound
114+
setHistorySort(field: SortParams<Swap>['field'], descending: boolean) {
115+
this.historySort = { field, descending };
116+
this._store.log.info('updated history list sort order', toJS(this.historySort));
117+
}
118+
101119
/**
102120
* initialized the settings and auto-save when a setting is changed
103121
*/
@@ -112,6 +130,7 @@ export default class SettingsStore {
112130
balanceMode: this.balanceMode,
113131
tourAutoShown: this.tourAutoShown,
114132
channelSort: toJS(this.channelSort),
133+
historySort: toJS(this.historySort),
115134
};
116135
this._store.storage.set('settings', settings);
117136
this._store.log.info('saved settings to localStorage', settings);
@@ -133,6 +152,7 @@ export default class SettingsStore {
133152
this.balanceMode = settings.balanceMode;
134153
this.tourAutoShown = settings.tourAutoShown;
135154
if (settings.channelSort) this.channelSort = settings.channelSort;
155+
if (settings.historySort) this.historySort = settings.historySort;
136156
this._store.log.info('loaded settings', settings);
137157
}
138158

app/src/store/stores/swapStore.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ export default class SwapStore {
3737

3838
/** swaps sorted by created date descending */
3939
@computed get sortedSwaps() {
40-
return values(this.swaps)
40+
const { field, descending } = this._store.settingsStore.historySort;
41+
const swaps = values(this.swaps)
4142
.slice()
42-
.sort((a, b) => b.initiationTime - a.initiationTime);
43+
.sort((a, b) => Swap.compare(a, b, field));
44+
45+
return descending ? swaps.reverse() : swaps;
4346
}
4447

4548
/** the last two swaps */

0 commit comments

Comments
 (0)