| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- # DefaultTableServerSide.vue
- <template>
- <q-table
- v-model:fullscreen="fullscreen"
- v-model:pagination="pagination"
- row-key="id"
- flat
- class="softpar-table q-pa-sm"
- :pagination-label="getPaginationLabel"
- :rows="rows"
- :rows-per-page-label="$t('common.ui.table.rows_per_page')"
- :columns="columns"
- :visible-columns="visibleColumns"
- :filter="pagination.filter"
- :grid="$q.screen.lt.sm"
- :loading="loading"
- v-bind="$attrs"
- @row-click="onRowClick"
- >
- <template #top>
- <div
- class="flex full-width justify-between items-center q-mb-md q-pl-sm"
- style="gap: 1rem"
- >
- <q-input
- v-if="showSearchField"
- v-model="pagination.filter"
- outlined
- dense
- debounce="500"
- :placeholder="$t('common.actions.search')"
- clearable
- autofocus
- >
- <template #append>
- <q-icon name="mdi-magnify" />
- </template>
- </q-input>
- <q-select
- v-if="showColumnsSelect"
- v-model="visibleColumns"
- class="q-ml-md"
- multiple
- dense
- outlined
- options-outlined
- :display-value="$q.lang.table.columns"
- emit-value
- map-options
- :options="mapColumns"
- style="min-width: 150px"
- options-selected-class="text-bold"
- />
- <q-space />
- <q-btn
- v-if="addItem"
- color="primary"
- padding="12px 16px"
- :outline="outlineAdd"
- :label="labelAdd"
- @click="onAddItem"
- />
- </div>
- </template>
- <template #body-cell-actions="{ row }">
- <q-td v-if="deleteFunction">
- <q-item-section>
- <q-btn
- color="negative"
- flat
- dense
- icon="mdi-delete"
- style="width: 45px"
- class="q-ml-auto q-mr-sm"
- @click.prevent.stop="onDelete(row.id)"
- />
- </q-item-section>
- </q-td>
- </template>
- <template v-if="!hideNoDataLabel" #no-data>
- <div class="q-my-md row justify-center full-width">
- <q-spinner v-if="loading" color="primary" size="30px" />
- <div v-else class="q-pa-md body2">
- {{ $t("http.errors.no_records_found") }}
- </div>
- </div>
- </template>
- <template #bottom="scope">
- <div class="flex full-width justify-end">
- <div class="flex items-center">
- {{ $t("common.ui.table.rows_per_page") }}
- <q-select
- v-model="pagination.rowsPerPage"
- class="q-mx-sm"
- dense
- borderless
- :options="rowsPerPageOptions"
- >
- <template #option="selectData">
- <q-item v-bind="selectData.itemProps">
- <q-item-section>
- <q-item-label>{{
- selectData.opt == 0 ? $t("common.ui.misc.all") : selectData.opt
- }}</q-item-label>
- </q-item-section>
- </q-item>
- </template>
- </q-select>
- </div>
- <div class="flex items-center">
- {{ pagination.from + "-" + pagination.to }} {{ $t("common.ui.table.of") }}
- {{ pagination.rowsNumber }}
- </div>
- <div class="flex items-center">
- <q-btn
- icon="mdi-chevron-left"
- color="grey-8"
- round
- dense
- flat
- :disable="scope.isFirstPage"
- @click="prevPage"
- />
- <q-btn
- icon="mdi-chevron-right"
- color="grey-8"
- round
- dense
- flat
- :disable="scope.isLastPage"
- @click="nextPage"
- />
- </div>
- </div>
- </template>
- <template v-for="name in $slots" #[name]="data">
- <slot :name="name" v-bind="data"></slot>
- </template>
- </q-table>
- </template>
- <script setup>
- import { ref, computed, onMounted, toRaw, watch } from "vue";
- import { useI18n } from "vue-i18n";
- import { useRouter } from "vue-router";
- const emit = defineEmits([
- "onRowClick",
- "onAddItem",
- "noRows",
- "togglePrincipal",
- ]);
- const {
- columns,
- apiCall,
- outlineAdd,
- openItem,
- openItemRoute,
- addItem,
- addItemRoute,
- rowsPerPage,
- showSearchField,
- hideNoDataLabel,
- deleteFunction,
- } = defineProps({
- columns: {
- type: Array,
- required: true,
- },
- apiCall: {
- type: Function,
- required: true,
- },
- labelAdd: {
- type: String,
- default: "Adicionar",
- },
- outlineAdd: {
- type: Boolean,
- default: false,
- },
- openItem: {
- type: Boolean,
- default: false,
- },
- openItemRoute: {
- type: String,
- default: "",
- },
- addItem: {
- type: Boolean,
- default: true,
- },
- addItemRoute: {
- type: String,
- default: "",
- },
- rowsPerPage: {
- type: Number,
- default: 10,
- },
- showColumnsSelect: {
- type: Boolean,
- default: false,
- },
- showSearchField: {
- type: Boolean,
- default: true,
- },
- noApiRoute: {
- type: Boolean,
- default: false,
- },
- hideNoDataLabel: {
- type: Boolean,
- default: false,
- },
- deleteFunction: {
- type: Function,
- default: null,
- },
- });
- const { t } = useI18n();
- const router = useRouter();
- const rows = ref([]);
- const loading = ref(true);
- const fullscreen = ref(false);
- const rowsPerPageOptions = [10, 15, 25, 50];
- const pagination = ref({
- filter: undefined,
- page: 1,
- rowsPerPage: rowsPerPage,
- rowsNumber: 0,
- from: 0,
- to: 0,
- });
- const mapColumns = computed(() => {
- return columns.reduce((accm, column) => {
- if (!column.required) {
- accm.push({
- label: column.label.toUpperCase(),
- value: column.name,
- });
- }
- return accm;
- }, []);
- });
- const visibleColumns = ref(mapColumns.value.map((column) => column.value));
- const onRowClick = (evt, row, index) => {
- const item = toRaw(row);
- if (openItem) {
- if (openItemRoute) {
- router.push({ name: openItemRoute, params: { id: item.id } });
- } else {
- emit("onRowClick", { evt, row, index });
- }
- }
- };
- const onAddItem = () => {
- if (addItem) {
- if (addItemRoute) {
- router.push({ name: addItemRoute });
- } else {
- emit("onAddItem");
- }
- }
- };
- const onDelete = async (id) => {
- if (deleteFunction) {
- loading.value = true;
- try {
- await deleteFunction(id);
- await onRequest();
- } catch (error) {
- console.error(error);
- } finally {
- loading.value = false;
- }
- }
- };
- const prevPage = () => {
- pagination.value.page--;
- onRequest();
- };
- const nextPage = () => {
- pagination.value.page++;
- onRequest();
- };
- const getPaginationLabel = (from, to, total) => {
- return `${from}-${to} ${t?.("common.ui.table.of") ?? "of"} ${total}`;
- };
- let isFetching = false;
- const onRequest = async () => {
- if (isFetching) return;
- isFetching = true;
- loading.value = true;
- try {
- const response = await apiCall({
- page: pagination.value.page,
- perPage: pagination.value.rowsPerPage,
- filter: pagination.value.filter,
- });
- rows.value = response.data.result.data;
- pagination.value.rowsNumber = response.data.result.total;
- pagination.value.from = response.data.result.from;
- pagination.value.to = response.data.result.to;
- if (rows.value.length === 0) {
- emit("noRows");
- }
- } catch (error) {
- console.error("Error fetching data:", error);
- } finally {
- loading.value = false;
- isFetching = false;
- }
- };
- watch(
- () => apiCall,
- () => onRequest(),
- );
- watch(
- pagination,
- async (newVal, oldVal) => {
- if (!oldVal || loading.value) return;
- if (
- newVal.rowsPerPage !== oldVal.rowsPerPage ||
- newVal.filter !== oldVal.filter ||
- newVal.page !== oldVal.page
- ) {
- await onRequest();
- }
- },
- { deep: true },
- );
- onMounted(async () => {
- await onRequest();
- });
- defineExpose({
- refresh: onRequest,
- });
- </script>
- <style lang="scss">
- @import "src/css/table.scss";
- </style>
|