Skip to content

Commit

Permalink
feat: New VaultsTable component and stories (#456)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoey-kaiser authored Sep 20, 2022
1 parent 68d63e0 commit be5f7f6
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 4 deletions.
45 changes: 45 additions & 0 deletions frontend/components/vault/VaultsTable.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { storiesOf } from '@storybook/vue';
import faker from 'faker';
import VaultsTable from './VaultsTable.vue';
import { generateFakeVaultTransactions } from '~/helpers/generateFakeVault';

const fakeVaultTransaction = generateFakeVaultTransactions();
const randomSelectedVault = faker.random.arrayElement(fakeVaultTransaction);

const common = {
components: { VaultsTable },
data: () => ({
vaultTransactions: fakeVaultTransaction,
selectedVaultId: randomSelectedVault.id,
lastUpdated: new Date(faker.date.recent()),
}),
};

storiesOf('Vault/VaultsTable', module)
.add('Plain', () => ({
...common,
template:
'<VaultsTable :vaultTransactions="vaultTransactions" :selectedVaultId="selectedVaultId" :last-updated="lastUpdated" />',
}))
.add('Fetching With Vaults', () => ({
...common,
template:
'<VaultsTable :vaultTransactions="vaultTransactions" :last-updated="lastUpdated" :is-loading="true" />',
}))
.add('Fetching without Vaults', () => ({
...common,
template: '<VaultsTable :last-updated="lastUpdated" :is-loading="true" />',
}))
.add('Empty vaults', () => ({
...common,
template: '<VaultsTable :last-updated="lastUpdated" />',
}))
.add('Error', () => ({
...common,
template: '<VaultsTable error="There was an error fetching the Vaults." />',
}))
.add('Expert Mode', () => ({
...common,
template:
'<VaultsTable :vaultTransactions="vaultTransactions" :selectedVaultId="selectedVaultId" show-more-rows :last-updated="lastUpdated" />',
}));
277 changes: 277 additions & 0 deletions frontend/components/vault/VaultsTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
<template>
<Loading :is-loading="showLoadingOverlay" :error="error">
<Table
ref="tableContainer"
:data-source="vaultTransactions"
:columns="columns"
:pagination="{ pageSize: numberOfRowsPerPage, hideOnSinglePage: true }"
:row-class-name="getRowClassNames"
:row-key="record => record.id"
:custom-row="customRowEvents"
:get-popup-container="() => $el"
:locale="{ emptyText: 'There are currently no vaults at risk.' }"
class="VaultsTable relative overflow-visible"
>
<div slot="collateralAmount" slot-scope="collateralAmount, record">
<format-currency
v-if="collateralAmount && record.collateralType"
:value="collateralAmount"
:currency="record.collateralType"
/>
<span v-else class="opacity-50">Unknown</span>
</div>
<div slot="proximityToLiquidation" slot-scope="proximityToLiquidation">
<template v-if="proximityToLiquidation">
<AnimatedNumber :value="proximityToLiquidation" />%
</template>
<span v-else class="opacity-50">Unknown</span>
</div>
<div slot="grossProfitDai" slot-scope="grossProfitDai">
<template v-if="grossProfitDai">
<format-currency :value="grossProfitDai" currency="DAI" />
</template>
<span v-else class="opacity-50">Unknown</span>
</div>
<div slot="nextPriceChange" slot-scope="nextPriceChange, record">
<div v-if="nextPriceChange" class="flex items-center space-x-2">
<AnimatedArrow :direction="getIsPriceGoingUpOrDown(record)" class="h-4 opacity-50" />
<TimeTill :date="nextPriceChange" />
</div>
<span v-else class="opacity-50">Unknown</span>
</div>
<div slot="updatingStatus" class="opacity-50 font-normal">
<div v-if="isLoading" class="flex items-center space-x-2">
<LoadingIcon class="h-4 w-4 animate animate-spin fill-current dark:text-gray-300" />
<span>Updating...</span>
</div>
<span v-else-if="lastUpdated"> Last updated <TimeTill :date="lastUpdated" /></span>
<span v-else> Last updated unknown time ago </span>
</div>
<div slot="action" slot-scope="text, record, index" class="w-full h-full">
<nuxt-link
:to="getVaultLink(record)"
:class="
(hoveredRowIndex === index && 'bg-primary text-white dark:bg-primary-dark') || 'text-primary'
"
class="flex items-center justify-center w-full h-full hover:text-white p-2 whitespace-nowrap"
>
<span v-if="record.state === 'liquidatable'"> Liquidate </span>
<span v-else> See details </span>
</nuxt-link>
</div>
</Table>
</Loading>
</template>

<script lang="ts">
import Vue, { PropType } from 'vue';
import { Table } from 'ant-design-vue';
import { VaultTransaction, VaultTransactionNotLiquidated } from 'auctions-core/src/types';
import { compareAsc } from 'date-fns';
import AnimatedArrow from '~/components/common/other/AnimatedArrow.vue';
import AnimatedNumber from '~/components/common/formatters/AnimatedNumber.vue';
import Loading from '~/components/common/other/Loading.vue';
import TimeTill from '~/components/common/formatters/TimeTill.vue';
import FormatCurrency from '~/components/common/formatters/FormatCurrency.vue';
import LoadingIcon from '~/assets/icons/loading.svg';
const compareBy = function (field: string, cmp: Function = (a: number, b: number): number => a - b): Function {
return (aVault: any, bVault: any, sortOrder: string) => {
const greaterVal = sortOrder === 'ascend' ? 1 : -1;
const aVal = aVault[field];
const bVal = bVault[field];
if (aVault.state === 'liquidated') {
return greaterVal;
}
if (bVault.state === 'liquidated') {
return -greaterVal;
}
if (typeof aVal === 'undefined') {
return greaterVal;
}
if (typeof bVal === 'undefined') {
return -greaterVal;
}
return cmp(aVal, bVal);
};
};
export default Vue.extend({
name: 'VaultsTable',
components: {
AnimatedArrow,
Loading,
Table,
TimeTill,
FormatCurrency,
LoadingIcon,
AnimatedNumber,
},
props: {
vaultTransactions: {
type: Array as PropType<VaultTransaction[]>,
default: () => [],
},
selectedVaultId: {
type: Number,
default: null,
},
showMoreRows: {
type: Boolean,
default: false,
},
isLoading: {
type: Boolean,
default: false,
},
lastUpdated: {
type: Date,
default: null,
},
error: {
type: String,
default: null,
},
},
data() {
return {
hoveredRowIndex: 0,
};
},
computed: {
columns(): Object[] {
const currencies = this.vaultTransactions.map(vault => vault.collateralType);
const uniqueCurrencies = Array.from(new Set(currencies));
const currenciesFilters = uniqueCurrencies.map(currency => ({
text: currency,
value: currency,
}));
return [
{
title: 'Index',
dataIndex: 'id',
sorter: compareBy('id'),
},
{
title: 'Collateral amount',
dataIndex: 'collateralAmount',
scopedSlots: { customRender: 'collateralAmount' },
sorter: compareBy('collateralAmount'),
filters: currenciesFilters,
onFilter: (selectedCurrency: string, vault: VaultTransaction) => {
return vault.collateralType.includes(selectedCurrency);
},
},
{
title: 'Proximity to liquidation threshold',
dataIndex: 'proximityToLiquidation',
scopedSlots: { customRender: 'proximityToLiquidation' },
sorter: compareBy('proximityToLiquidation'),
defaultSortOrder: 'ascend',
},
{
title: 'Potential profit',
dataIndex: 'grossProfitDai',
scopedSlots: { customRender: 'grossProfitDai' },
sorter: compareBy('grossProfitDai'),
},
{
title: 'Next price update',
dataIndex: 'nextPriceChange',
scopedSlots: { customRender: 'nextPriceChange' },
sorter: compareBy('nextPriceChange', (a: Date, b: Date) => compareAsc(a, b)),
},
{
slots: { title: 'updatingStatus', customRender: 'action' },
scopedSlots: { customRender: 'action' },
width: '20%',
},
];
},
numberOfRowsPerPage(): number {
return this.showMoreRows ? 15 : 10;
},
showLoadingOverlay(): boolean {
return this.isLoading && this.vaultTransactions.length === 0;
},
},
methods: {
customRowEvents(record: VaultTransaction, rowIndex: number): Object {
return {
on: {
click: () => {
this.$router?.push(this.getVaultLink(record));
},
mouseenter: () => {
this.hoveredRowIndex = rowIndex;
},
},
};
},
getRowClassNames(vault: VaultTransaction) {
const classes = [];
if (this.selectedVaultId === vault.id) {
classes.push('selected-row');
}
if (!this.getIsVaultFinished(vault)) {
classes.push('bg-gray-100 dark:bg-gray-800');
}
return classes.join(' ');
},
getVaultLink(vault: VaultTransaction) {
const searchParams = new URLSearchParams({
network: vault.network,
vault: `${vault.collateralType}:${vault.id}`,
});
return `/vaults?${searchParams.toString()}`;
},
getIsVaultFinished(vault: VaultTransaction) {
return vault.state !== 'liquidated';
},
getIsPriceGoingUpOrDown(vault: VaultTransactionNotLiquidated) {
if (vault.nextUnitPrice.isGreaterThan(vault.currentUnitPrice)) {
return 'up';
}
return 'down';
},
},
});
</script>

<style scoped>
.VaultsTable >>> .ant-table-placeholder {
min-height: 100px;
}
.VaultsTable >>> .ant-table-thead th {
@apply py-1 px-2 h-8 bg-transparent text-gray-700 font-bold border-0 border-t-2 dark:text-gray-100;
}
.VaultsTable >>> .ant-table-tbody td {
@apply py-1 px-2 h-8 text-gray-500 border-0 dark:text-gray-300;
}
.VaultsTable >>> .ant-table-tbody tr > *:last-child {
@apply p-0;
}
.VaultsTable >>> .ant-table-tbody td,
.VaultsTable >>> .ant-table-thead th {
@apply border-b-2 border-r-2 border-gray-300 dark:border-gray-600;
}
.VaultsTable >>> .selected-row {
@apply bg-gray-200 dark:bg-dark-light;
}
.VaultsTable >>> .selected-row td {
@apply text-gray-700;
}
.VaultsTable >>> .ant-table-tbody td:first-child,
.VaultsTable >>> .ant-table-thead th:first-child {
@apply border-l-2;
}
.VaultsTable >>> .ant-table-thead tr:hover th {
@apply dark:bg-dark-light;
}
.VaultsTable >>> .ant-table-tbody tr:hover td {
@apply dark:bg-dark-light;
}
.VaultsTable >>> .ant-dropdown-menu-item {
@apply mb-1;
}
</style>
10 changes: 6 additions & 4 deletions frontend/helpers/generateFakeVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Vault,
VaultAmount,
VaultBase,
VaultTransaction,
VaultTransactionFees,
VaultTransactionLiquidated,
VaultTransactionNotLiquidated,
Expand Down Expand Up @@ -148,8 +149,9 @@ export const generateFakeVaultTransactions = function (
liquidatedVaultsAmount = random(1, 5),
notLiquidatedVaultsAmount = random(5, 15)
) {
const vaults = [];
vaults.push(Array(liquidatedVaultsAmount).fill(null).map(generateFakeVaultLiquidatedTransaction));
vaults.push(Array(notLiquidatedVaultsAmount).fill(null).map(generateFakeVaultNotLiquidatedTransaction));
return vaults;
const vaults: VaultTransaction[] = [
...Array(liquidatedVaultsAmount).fill(null).map(generateFakeVaultLiquidatedTransaction),
...Array(notLiquidatedVaultsAmount).fill(null).map(generateFakeVaultNotLiquidatedTransaction),
];
return faker.helpers.shuffle(vaults);
};

0 comments on commit be5f7f6

Please sign in to comment.