Skip to content

Commit

Permalink
Add pagination to visualizations grid
Browse files Browse the repository at this point in the history
  • Loading branch information
guerler committed Oct 20, 2023
1 parent 38a8af1 commit e92cab2
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 44 deletions.
2 changes: 1 addition & 1 deletion client/src/components/Grid/GridElements/GridText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ defineProps<Props>();
</script>

<template>
<span v-localize>{{ text }}</span>
<span>{{ text }}</span>
</template>
131 changes: 91 additions & 40 deletions client/src/components/Grid/GridList.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import axios from "axios";
import { BAlert } from "bootstrap-vue";
import { computed, onMounted, ref } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router/composables";
import { withPrefix } from "@/utils/redirect";
Expand All @@ -12,6 +12,7 @@ import { FieldKeyHandler, Operation, RowData } from "./configs/types";
import GridOperations from "./GridElements/GridOperations.vue";
import GridSharing from "./GridElements/GridSharing.vue";
import GridText from "./GridElements/GridText.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
//@ts-ignore
import UtcDate from "@/components/UtcDate.vue";
Expand All @@ -21,9 +22,13 @@ const router = useRouter();
interface Props {
// specifies the grid config identifier as specified in the registry
id: string;
// rows per page to be shown
perPage?: number;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
perPage: 5,
});
// contains the current grid data provided by the corresponding api endpoint
const gridData = ref();
Expand All @@ -33,22 +38,37 @@ const errorMessage = ref("");
const operationMessage = ref("");
const operationStatus = ref("");
// page references
const currentPage = ref(1);
const totalRows = ref(0);
// loading indicator
const loading = ref(true);
// current grid configuration
const gridConfig = computed(() => {
return registry[props.id];
});
// check if loading has completed and data rows are available
const isAvailable = computed(() => !loading.value && totalRows.value > 0);
/**
* Request grid data
*/
async function getGridData() {
if (gridConfig.value) {
try {
const { data } = await axios.get(withPrefix(gridConfig.value.url));
gridData.value = data;
const response = await axios.get(withPrefix(gridConfig.value.getUrl(currentPage.value, props.perPage, "")));
if (response.headers.total_matches) {
totalRows.value = parseInt(response.headers.total_matches);
}
gridData.value = response.data;
errorMessage.value = "";
loading.value = false;
} catch (e) {
errorMessage.value = "Failed to obtain grid data.";
loading.value = false;
}
}
}
Expand Down Expand Up @@ -81,66 +101,97 @@ async function onTagInput(data: RowData, tags: Array<string>, tagsHandler: Field
}
function onTagClick() {}
watch(currentPage, () => getGridData());
</script>

<template>
<div class="grid-list">
<BAlert v-if="!!errorMessage" variant="danger" show>{{ errorMessage }}</BAlert>
<BAlert v-if="!!operationMessage" :variant="operationStatus" show>{{ operationMessage }}</BAlert>
<div>
<h1 class="grid-header pb-3 h-lg">
<div class="grid-header">
<h1>
{{ gridConfig.title }}
</h1>
<table class="table">
<thead>
<th v-for="(fieldEntry, fieldIndex) in gridConfig.fields" :key="fieldIndex">
{{ fieldEntry.title || fieldEntry.key }}
</th>
</thead>
<tr
v-for="(rowData, rowIndex) in gridData"
:key="rowIndex"
:class="{ 'grid-list-dark-row': rowIndex % 2 }">
<td v-for="(fieldEntry, fieldIndex) in gridConfig.fields" :key="fieldIndex">
<GridOperations
v-if="fieldEntry.type == 'operations'"
:title="rowData.title"
:operations="fieldEntry.operations"
@execute="executeOperation($event, rowData)" />
<GridText v-else-if="fieldEntry.type == 'text'" :text="rowData[fieldEntry.key]" />
<GridSharing
v-else-if="fieldEntry.type == 'sharing'"
:published="rowData.sharing_status.published"
:importable="rowData.sharing_status.importable"
:users_shared_with_length="rowData.sharing_status.users_shared_with.length" />
<UtcDate v-else-if="fieldEntry.type == 'date'" :date="rowData[fieldEntry.key]" mode="elapsed" />
<StatelessTags
v-else-if="fieldEntry.type == 'tags'"
clickable
:value="rowData[fieldEntry.key]"
:disabled="rowData.published"
@input="(tags) => onTagInput(rowData, tags, fieldEntry.handler)"
@tag-click="onTagClick" />
</td>
</tr>
</table>
</div>
<LoadingSpan v-if="loading" />
<BAlert v-else-if="!isAvailable" v-localize variant="info" show>No entries found.</BAlert>
<table v-else class="grid-table">
<thead>
<th v-for="(fieldEntry, fieldIndex) in gridConfig.fields" :key="fieldIndex" class="px-2">
{{ fieldEntry.title || fieldEntry.key }}
</th>
</thead>
<tr v-for="(rowData, rowIndex) in gridData" :key="rowIndex" :class="{ 'grid-dark-row': rowIndex % 2 }">
<td v-for="(fieldEntry, fieldIndex) in gridConfig.fields" :key="fieldIndex" class="px-2 py-3">
<GridOperations
v-if="fieldEntry.type == 'operations'"
:title="rowData.title"
:operations="fieldEntry.operations"
@execute="executeOperation($event, rowData)" />
<GridText v-else-if="fieldEntry.type == 'text'" :text="rowData[fieldEntry.key]" />
<GridSharing
v-else-if="fieldEntry.type == 'sharing'"
:published="rowData.sharing_status.published"
:importable="rowData.sharing_status.importable"
:users_shared_with_length="rowData.sharing_status.users_shared_with.length" />
<UtcDate v-else-if="fieldEntry.type == 'date'" :date="rowData[fieldEntry.key]" mode="elapsed" />
<StatelessTags
v-else-if="fieldEntry.type == 'tags'"
clickable
:value="rowData[fieldEntry.key]"
:disabled="rowData.published"
@input="(tags) => onTagInput(rowData, tags, fieldEntry.handler)"
@tag-click="onTagClick" />
</td>
</tr>
</table>
<div class="flex-grow-1 h-100" />
<div v-if="isAvailable" class="grid-footer">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
size="sm"
aria-controls="grid-table" />
</div>
</div>
</template>

<style lang="scss">
@import "theme/blue.scss";
@import "~bootstrap/scss/bootstrap.scss";
.grid-list {
@extend .d-flex;
@extend .flex-column;
overflow: auto;
.grid-footer {
@extend .d-flex;
@extend .grid-sticky;
@extend .justify-content-center;
@extend .pt-3;
position: sticky;
bottom: 0;
.pagination {
@extend .m-0;
}
}
.grid-header {
@extend .grid-sticky;
@extend .pb-3;
position: sticky;
top: 0;
h1 {
@extend .m-0;
}
}
.grid-sticky {
z-index: 1;
background: $white;
opacity: 0.95;
}
.grid-list-dark-row {
.grid-dark-row {
background: $gray-200;
}
}
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Grid/configs/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type Field = FieldKey | FieldOperations;

type FieldType = "date" | "operations" | "sharing" | "tags" | "text" | undefined;
// TODO: type FieldType = "date" | "operations" | "sharing" | "tags" | "text" | undefined;

interface FieldKey {
key?: string;
Expand All @@ -16,7 +16,7 @@ interface OperationHandlerMessage {
type OperationHandlerReturn = OperationHandlerMessage | void;

export interface Config {
url: string;
getUrl: (currentPage: number, perPage: number, search: string) => string;
resource: string;
item: string;
plural: string;
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/Grid/configs/visualizations.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { withPrefix } from "@/utils/redirect";
import { errorMessageAsString, rethrowSimple } from "@/utils/simple-error";

export const VisualizationsGrid = {
url: "/api/visualizations/detailed",
getUrl: (currentPage, perPage, searchTerm) => {
const offset = perPage * (currentPage - 1);
return `/api/visualizations/detailed?limit=${perPage}&offset=${offset}`;
},
resource: "visualizations",
item: "visualization",
plural: "Visualizations",
Expand Down

0 comments on commit e92cab2

Please sign in to comment.