web/satellite/vuetify-poc: add client-side sorting for buckets table

Sorting works only if buckets fit on single page (can be changed by modifing limit value).
We use custom sorting approach to sort by correct value type and not by string.
We don't use custom-key-sort prop of VDataTableServer component because it doesn't work (probably expects it to be done on backend side because VDataTable component's prop works as expected).

Issue:
https://github.com/storj/storj/issues/6342

Change-Id: I0cfbdf432e255f530457c89253a7f29b8e1cbc30
This commit is contained in:
Vitalii 2023-10-03 16:54:12 +03:00 committed by Storj Robot
parent 7f02b73b5d
commit 60bf9531af

View File

@ -18,19 +18,19 @@
/>
<v-data-table-server
v-model="selected"
:sort-by="sortBy"
:headers="headers"
:items="page.buckets"
:items="displayedItems"
:search="search"
:loading="areBucketsFetching"
:items-length="page.totalCount"
:items-per-page-options="tableSizeOptions(page.totalCount)"
item-value="name"
no-data-text="No results found"
class="elevation-1"
hover
@update:itemsPerPage="onUpdateLimit"
@update:page="onUpdatePage"
@update:sortBy="onUpdateSort"
>
<template #item.name="{ item }">
<v-btn
@ -68,7 +68,7 @@
{{ item.raw.segmentCount.toLocaleString() }}
</span>
</template>
<template #item.createdAt="{ item }">
<template #item.since="{ item }">
<span>
{{ item.raw.since.toLocaleString() }}
</span>
@ -128,7 +128,7 @@ import { useRouter } from 'vue-router';
import { VCard, VTextField, VBtn, VMenu, VList, VListItem, VListItemTitle, VDivider } from 'vuetify/components';
import { VDataTableServer } from 'vuetify/labs/components';
import { BucketPage, BucketCursor } from '@/types/buckets';
import { BucketPage, BucketCursor, Bucket } from '@/types/buckets';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
@ -157,8 +157,6 @@ const FIRST_PAGE = 1;
const areBucketsFetching = ref<boolean>(true);
const search = ref<string>('');
const searchTimer = ref<NodeJS.Timeout>();
const selected = ref([]);
const bucketDetailsName = ref<string>('');
const shareBucketName = ref<string>('');
const isDeleteBucketDialogShown = ref<boolean>(false);
@ -167,9 +165,15 @@ const isBucketPassphraseDialogOpen = ref(false);
const isShareBucketDialogShown = ref<boolean>(false);
const isBucketDetailsDialogShown = ref<boolean>(false);
const pageWidth = ref<number>(document.body.clientWidth);
const sortBy = ref<SortItem[] | undefined>([{ key: 'name', order: 'asc' }]);
let passphraseDialogCallback: () => void = () => {};
type SortItem = {
key: keyof Bucket;
order: boolean | 'asc' | 'desc';
}
type DataTableHeader = {
key: string;
title: string;
@ -178,19 +182,31 @@ type DataTableHeader = {
width?: number | string;
}
const displayedItems = computed<Bucket[]>(() => {
const items = page.value.buckets;
sort(items, sortBy.value);
return items;
});
const isTableSortable = computed<boolean>(() => {
return page.value.totalCount <= cursor.value.limit;
});
const headers = computed<DataTableHeader[]>(() => {
const hdrs: DataTableHeader[] = [
{
title: 'Name',
align: 'start',
key: 'name',
sortable: false,
sortable: isTableSortable.value,
},
{ title: 'Storage', key: 'storage', sortable: false },
{ title: 'Download', key: 'egress', sortable: false },
{ title: 'Files', key: 'objectCount', sortable: false },
{ title: 'Segments', key: 'segmentCount', sortable: false },
{ title: 'Date Created', key: 'createdAt', sortable: false },
{ title: 'Storage', key: 'storage', sortable: isTableSortable.value },
{ title: 'Download', key: 'egress', sortable: isTableSortable.value },
{ title: 'Files', key: 'objectCount', sortable: isTableSortable.value },
{ title: 'Segments', key: 'segmentCount', sortable: isTableSortable.value },
{ title: 'Date Created', key: 'since', sortable: isTableSortable.value },
{ title: '', key: 'actions', width: '0', sortable: false },
];
@ -263,6 +279,41 @@ async function fetchBuckets(page = FIRST_PAGE, limit = DEFAULT_PAGE_LIMIT): Prom
}
}
/**
* Sorts items by provided sort options.
* We use this to correctly sort columns by value of correct type.
* @param items
* @param sortOptions
*/
function sort(items: Bucket[], sortOptions: SortItem[] | undefined): void {
if (!(sortOptions && sortOptions.length)) {
items.sort((a, b) => a.name.localeCompare(b.name));
return;
}
const option = sortOptions[0];
switch (option.key) {
case 'egress':
items.sort((a, b) => option.order === 'asc' ? a.egress - b.egress : b.egress - a.egress);
break;
case 'storage':
items.sort((a, b) => option.order === 'asc' ? a.storage - b.storage : b.storage - a.storage);
break;
case 'objectCount':
items.sort((a, b) => option.order === 'asc' ? a.objectCount - b.objectCount : b.objectCount - a.objectCount);
break;
case 'segmentCount':
items.sort((a, b) => option.order === 'asc' ? a.segmentCount - b.segmentCount : b.segmentCount - a.segmentCount);
break;
case 'since':
items.sort((a, b) => option.order === 'asc' ? a.since.getTime() - b.since.getTime() : b.since.getTime() - a.since.getTime());
break;
default:
items.sort((a, b) => option.order === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name));
}
}
/**
* Handles update table rows limit event.
*/
@ -277,6 +328,13 @@ function onUpdatePage(page: number): void {
fetchBuckets(page, cursor.value.limit);
}
/**
* Handles update table sorting event.
*/
function onUpdateSort(value: SortItem[]): void {
sortBy.value = value;
}
/**
* Navigates to bucket page.
*/
@ -352,6 +410,10 @@ watch(() => search.value, () => {
}, 500); // 500ms delay for every new call.
});
watch(() => page.value.totalCount, () => {
sortBy.value = [{ key: 'name', order: 'asc' }];
});
onMounted(() => {
window.addEventListener('resize', resizeHandler);