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

Added VaultsTable and stories #456

Merged
merged 12 commits into from
Sep 20, 2022
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 auctions', () => ({
zoey-kaiser marked this conversation as resolved.
Show resolved Hide resolved
...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);
};