forked from verdnatura/salix-front
206 lines
4.9 KiB
Vue
206 lines
4.9 KiB
Vue
<script setup>
|
|
import axios from 'axios';
|
|
import { onMounted, ref, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const { t } = useI18n();
|
|
|
|
const $props = defineProps({
|
|
autoLoad: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
url: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
data: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
filter: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
where: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
sortBy: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
limit: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
rowsPerPage: {
|
|
type: Number,
|
|
default: 10,
|
|
},
|
|
offset: {
|
|
type: Number,
|
|
default: 500,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['onFetch', 'onPaginate']);
|
|
defineExpose({ refresh });
|
|
|
|
onMounted(() => {
|
|
if ($props.autoLoad) paginate();
|
|
});
|
|
|
|
watch(
|
|
() => $props.data,
|
|
() => {
|
|
rows.value = $props.data;
|
|
}
|
|
);
|
|
|
|
const isLoading = ref(false);
|
|
const hasMoreData = ref(false);
|
|
const pagination = ref({
|
|
sortBy: $props.sortBy,
|
|
rowsPerPage: $props.rowsPerPage,
|
|
page: 1,
|
|
});
|
|
const rows = ref(null);
|
|
|
|
async function fetch() {
|
|
const { page, rowsPerPage, sortBy } = pagination.value;
|
|
|
|
if (!$props.url) return;
|
|
|
|
const filter = {
|
|
limit: rowsPerPage,
|
|
skip: rowsPerPage * (page - 1),
|
|
};
|
|
|
|
Object.assign(filter, $props.filter);
|
|
|
|
if ($props.where) filter.where = $props.where;
|
|
if ($props.sortBy) filter.order = $props.sortBy;
|
|
if ($props.limit) filter.limit = $props.limit;
|
|
|
|
if (sortBy) filter.order = sortBy;
|
|
|
|
const { data } = await axios.get($props.url, {
|
|
params: { filter },
|
|
});
|
|
|
|
isLoading.value = false;
|
|
|
|
return data;
|
|
}
|
|
|
|
async function paginate() {
|
|
const { page, rowsPerPage, sortBy, descending } = pagination.value;
|
|
|
|
const data = await fetch();
|
|
|
|
if (!data) {
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
hasMoreData.value = data.length === rowsPerPage;
|
|
|
|
if (!rows.value) rows.value = [];
|
|
for (const row of data) rows.value.push(row);
|
|
|
|
pagination.value.rowsNumber = rows.value.length;
|
|
pagination.value.page = page;
|
|
pagination.value.rowsPerPage = rowsPerPage;
|
|
pagination.value.sortBy = sortBy;
|
|
pagination.value.descending = descending;
|
|
|
|
isLoading.value = false;
|
|
|
|
emit('onFetch', rows);
|
|
emit('onPaginate', data);
|
|
}
|
|
|
|
async function refresh() {
|
|
const { rowsPerPage } = pagination.value;
|
|
|
|
const data = await fetch();
|
|
|
|
if (!data) {
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
hasMoreData.value = data.length === rowsPerPage;
|
|
|
|
if (!rows.value) rows.value = [];
|
|
rows.value = data;
|
|
|
|
isLoading.value = false;
|
|
|
|
emit('onFetch', rows);
|
|
}
|
|
|
|
async function onLoad(...params) {
|
|
const done = params[1];
|
|
if (!rows.value || rows.value.length === 0 || !$props.url) return done(false);
|
|
|
|
pagination.value.page = pagination.value.page + 1;
|
|
|
|
await paginate();
|
|
|
|
const endOfPages = !hasMoreData.value;
|
|
done(endOfPages);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<q-infinite-scroll @load="onLoad" :offset="offset" class="column items-center">
|
|
<div v-if="rows" class="card-list q-gutter-y-md">
|
|
<slot name="body" :rows="rows"></slot>
|
|
<div v-if="!rows.length && !isLoading" class="info-row q-pa-md text-center">
|
|
<h5>
|
|
{{ t('components.smartCard.noData') }}
|
|
</h5>
|
|
</div>
|
|
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
|
<q-spinner color="orange" size="md" />
|
|
</div>
|
|
</div>
|
|
<div v-if="!rows" class="card-list q-gutter-y-md">
|
|
<q-card class="card" v-for="$index in $props.rowsPerPage" :key="$index">
|
|
<q-item v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
|
<q-item-section class="q-pa-md">
|
|
<q-skeleton type="rect" class="q-mb-md" square />
|
|
<q-skeleton type="text" square />
|
|
<q-skeleton type="text" class="q-mb-md" square />
|
|
<q-skeleton type="text" square />
|
|
<q-skeleton type="text" square />
|
|
</q-item-section>
|
|
<q-separator vertical />
|
|
<q-card-actions vertical class="justify-between">
|
|
<q-skeleton type="circle" class="q-mb-md" size="40px" />
|
|
<q-skeleton type="circle" class="q-mb-md" size="40px" />
|
|
<q-skeleton type="circle" class="q-mb-md" size="40px" />
|
|
</q-card-actions>
|
|
</q-item>
|
|
</q-card>
|
|
</div>
|
|
</q-infinite-scroll>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.card-list {
|
|
width: 100%;
|
|
max-width: 60em;
|
|
}
|
|
|
|
.info-row {
|
|
width: 100%;
|
|
|
|
h5 {
|
|
margin: 0;
|
|
}
|
|
}
|
|
</style>
|