| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- <template>
- <q-table
- v-model:fullscreen="fullscreen"
- flat
- row-key="id"
- :pagination="{ rowsPerPage }"
- :pagination-label="getPaginationLabel"
- :rows-per-page-label="$t('common.ui.table.rows_per_page')"
- :grid="$q.screen.lt.sm"
- :visible-columns
- :filter
- :columns
- :loading
- :rows
- class="softpar-table q-pa-sm q-mt-md"
- @row-click="onRowClick"
- >
- <template #top>
- <div
- class="flex full-width align-center q-mb-md q-pl-sm"
- style="gap: 1rem"
- >
- <div v-if="title" class="column text-h6">
- <span>{{ title }}</span>
- <span class="text-body2">{{
- `${rows.length} ${description} ${female ? "cadastradas" : "cadastrados"}`
- }}</span>
- </div>
- <slot name="top" :rows="rows" />
- <q-space />
- <q-btn
- v-if="addItem"
- color="primary"
- style="width: 40px; height: 40px"
- icon="mdi-plus"
- :outline="outlineAdd"
- @click="onAddItem"
- >
- </q-btn>
- </div>
- <div class="flex full-width align-center q-mb-md" style="gap: 1rem">
- <DefaultInput
- v-if="showSearchField"
- v-model="filter"
- debounce="250"
- label="Busque por Unidade, status ou Responsavel"
- label-color="dark"
- :placeholder="$t('common.actions.search')"
- clearable
- class="q-mt-sm full-width search-input"
- color="dark"
- bg-color="transparent"
- dense
- input-class="q-pl-xs"
- >
- <template #prepend>
- <q-icon name="mdi-magnify" color="grey-6" />
- </template>
- </DefaultInput>
- <DefaultSelect
- v-if="showColumnsSelect"
- v-model="visibleColumns"
- multiple
- options-outlined
- :display-value="$q.lang.table.columns"
- emit-value
- map-options
- :options="mapColumns"
- style="width: 150px"
- options-selected-class="text-bold"
- />
- </div>
- </template>
- <template #body-cell-actions="{ row }">
- <q-td auto-width>
- <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
- <slot name="body-cell-actions" :row="row" />
- <q-btn
- v-if="openItemRoute"
- outline
- icon="mdi-pencil-outline"
- style="width: 36px"
- @click.prevent.stop="
- router.push({ name: openItemRoute, params: { id: row.id } })
- "
- />
- <q-btn
- v-if="deleteFunction"
- outline
- icon="mdi-trash-can-outline"
- style="width: 36px"
- color="negative"
- @click.prevent.stop="onDelete(row.id)"
- />
- </q-item-section>
- </q-td>
- </template>
- <template #item="{ cols, row, index }">
- <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3">
- <q-card
- bordered
- flat
- class="q-pa-sm"
- @click="onRowClick($event, row, index)"
- >
- <q-list dense>
- <q-item
- v-for="col in cols.filter((col) => col.name !== 'desc')"
- :key="col.name"
- >
- <template v-if="col.name !== 'actions'">
- <q-item-section>
- <q-item-label caption>{{ col.label }}</q-item-label>
- <q-item-label>{{ col.value }}</q-item-label>
- </q-item-section>
- </template>
- <template v-else>
- <slot name="body-cell-actions" :row="row" />
- <q-item-section
- v-if="openItemRoute || deleteFunction"
- style="flex-direction: row; gap: 4px"
- >
- <q-btn
- v-if="openItemRoute"
- outline
- icon="mdi-pencil-outline"
- style="width: 36px"
- @click.prevent.stop="
- router.push({
- name: openItemRoute,
- params: { id: row.id },
- })
- "
- />
- <q-btn
- v-if="deleteFunction"
- outline
- icon="mdi-trash-can-outline"
- style="width: 36px"
- color="negative"
- @click.prevent.stop="onDelete(row.id)"
- />
- </q-item-section>
- </template>
- </q-item>
- </q-list>
- </q-card>
- </div>
- </template>
- <template #loading>
- <q-inner-loading showing color="primary" />
- </template>
- <template v-if="!hideNoDataLabel" #no-data>
- <div v-if="!loading" class="q-my-md row justify-center full-width">
- <div class="q-pa-md body2">
- {{ $t("http.errors.no_records_found") }}
- </div>
- </div>
- </template>
- <template v-for="slotName in usableSlots($slots)" #[slotName]="data">
- <slot :name="slotName" v-bind="data" />
- </template>
- </q-table>
- </template>
- <script setup>
- import { ref, onMounted, toRaw, watch } from "vue";
- import { useI18n } from "vue-i18n";
- import { useRouter } from "vue-router";
- import { useQuasar } from "quasar";
- import DefaultInput from "./DefaultInput.vue";
- import DefaultSelect from "./DefaultSelect.vue";
- const emit = defineEmits(["onRowClick", "onAddItem", "noRows"]);
- const {
- columns,
- apiCall,
- outlineAdd,
- openItem,
- openItemRoute,
- addItem,
- addItemRoute,
- rowsPerPage,
- showSearchField,
- noApiCall,
- hideNoDataLabel,
- deleteFunction,
- } = defineProps({
- title: {
- type: String,
- default: null,
- },
- columns: {
- type: Array,
- required: true,
- },
- apiCall: {
- type: Function,
- default: null,
- },
- description: {
- type: String,
- default: "linhas",
- },
- female: {
- type: Boolean,
- default: true,
- },
- outlineAdd: {
- type: Boolean,
- default: false,
- },
- openItem: {
- type: Boolean,
- default: false,
- },
- openItemRoute: {
- type: String,
- default: "",
- },
- addItem: {
- type: Boolean,
- default: false,
- },
- addItemRoute: {
- type: String,
- default: "",
- },
- rowsPerPage: {
- type: Number,
- default: 10,
- },
- showSearchField: {
- type: Boolean,
- default: true,
- },
- showColumnsSelect: {
- type: Boolean,
- default: false,
- },
- noApiCall: {
- type: Boolean,
- default: false,
- },
- hideNoDataLabel: {
- type: Boolean,
- default: false,
- },
- deleteFunction: {
- type: Function,
- default: null,
- },
- });
- const router = useRouter();
- const { t } = useI18n();
- const $q = useQuasar();
- const rows = defineModel("rows", { type: Array, default: [] });
- const filter = ref("");
- const loading = ref(true);
- const fullscreen = ref(false);
- const mapColumns = columns.reduce((accm, column) => {
- if (!column.required) {
- accm.push({
- label: column.label,
- value: column.name,
- });
- }
- return accm;
- }, []);
- const visibleColumns = ref(mapColumns.map((column) => column.value));
- const getPaginationLabel = (from, to, last) => {
- return `${from}-${to} ${t("common.ui.table.of")} ${last}`;
- };
- 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) {
- $q.dialog({
- title: t("common.ui.messages.confirm_action"),
- message: t("common.ui.messages.are_you_sure_delete"),
- ok: {
- color: "negative",
- label: t("common.actions.delete"),
- },
- cancel: {
- color: "primary",
- outline: true,
- label: t("common.actions.cancel"),
- },
- }).onOk(async () => {
- loading.value = true;
- try {
- await deleteFunction(id);
- await onRequest();
- } catch (error) {
- console.error(error);
- } finally {
- loading.value = false;
- }
- });
- }
- };
- const onRequest = async () => {
- if (noApiCall) {
- loading.value = false;
- return;
- }
- loading.value = true;
- const response = await apiCall();
- rows.value = response;
- if (rows.value.length == 0) {
- emit("noRows");
- }
- loading.value = false;
- };
- const usableSlots = (slots) => {
- const availableSlots = Object.keys(slots);
- const usableSlots = availableSlots.filter(
- (slot) =>
- !["body-cell-actions", "top", "loading", "no-data"].includes(slot),
- );
- return usableSlots;
- };
- watch(
- () => apiCall,
- async () => {
- await onRequest();
- },
- );
- onMounted(async () => {
- await onRequest({
- filter: undefined,
- });
- });
- defineExpose({
- refresh: onRequest,
- });
- </script>
- <style lang="scss">
- @import "src/css/table.scss";
- </style>
|