DefaultTable.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <template>
  2. <q-table
  3. v-model:fullscreen="fullscreen"
  4. flat
  5. :pagination="{ rowsPerPage }"
  6. :pagination-label="getPaginationLabel"
  7. row-key="id"
  8. :rows="rows"
  9. :rows-per-page-label="$t('general.rows_per_page')"
  10. :columns="props.columns"
  11. :visible-columns="visibleColumns"
  12. :filter="filter"
  13. :grid="$q.screen.lt.sm"
  14. class="softpar-table q-pa-sm"
  15. @row-click="onRowClick"
  16. >
  17. <template #top>
  18. <div
  19. class="flex full-width justify-between align-center q-mb-md q-pl-sm"
  20. style="gap: 1rem"
  21. >
  22. <q-input
  23. v-if="mostrarCampoPesquisa"
  24. v-model="filter"
  25. debounce="250"
  26. :placeholder="$t('general.search')"
  27. clearable
  28. autofocus
  29. class=""
  30. color="primary"
  31. >
  32. <template #append>
  33. <q-icon name="mdi-magnify" />
  34. </template>
  35. </q-input>
  36. <q-select
  37. v-if="mostrarSelecaoDeColunas"
  38. v-model="visibleColumns"
  39. multiple
  40. dense
  41. outlined
  42. options-outlined
  43. :display-value="$q.lang.table.columns"
  44. emit-value
  45. map-options
  46. :options="mapColuns"
  47. option-value="name"
  48. style="width: 150px"
  49. options-selected-class="text-bold"
  50. />
  51. <q-btn
  52. v-if="mostrarBotaoFullscreen"
  53. flat
  54. @click="fullscreen = !fullscreen"
  55. >
  56. <q-icon name="mdi-fullscreen" />
  57. </q-btn>
  58. <q-space />
  59. <q-btn-dropdown
  60. v-if="props.dropDown"
  61. color="primary"
  62. :label="$t('general.options')"
  63. />
  64. <q-btn
  65. v-if="props.addItem"
  66. color="primary"
  67. padding="10px 16px"
  68. :outline="props.outlineAdd"
  69. :label="$t('general.add')"
  70. @click="onAddItem"
  71. >
  72. </q-btn>
  73. </div>
  74. </template>
  75. <template #body-cell-status="{ value, row }">
  76. <q-td style="width: 8%">
  77. <q-item-section>
  78. <span class="text-center">
  79. <div v-if="row.status && value" class="ativo body2 text-positive">
  80. {{ $t("general.active") }}
  81. </div>
  82. <div v-if="!row.status" class="inativo body2 text-accent">
  83. {{ $t("general.inactive") }}
  84. </div>
  85. </span>
  86. </q-item-section>
  87. </q-td>
  88. </template>
  89. <template #body-cell-ativo="{ value, row }">
  90. <q-td style="width: 8%">
  91. <q-item-section>
  92. <span class="text-center">
  93. <div v-if="row.ativo && value" class="ativo body2 text-positive">
  94. {{ $t("general.active") }}
  95. </div>
  96. <div v-if="row.ativo && !value" class="ativo body2 text-positive">
  97. {{ $t("general.active") }}
  98. </div>
  99. <div v-if="!row.ativo" class="inativo body2 text-accent">
  100. {{ $t("general.active") }}
  101. </div>
  102. </span>
  103. </q-item-section>
  104. </q-td>
  105. </template>
  106. <template v-if="!props.hideNoDataLabel" #no-data>
  107. <div class="q-my-md row justify-center full-width">
  108. <q-spinner v-if="loading" color="primary" size="30px" />
  109. <div v-else class="q-pa-md body2">
  110. {{ $t("errors.no_records_found") }}
  111. </div>
  112. </div>
  113. </template>
  114. <template v-for="(index, name) in $slots" #[name]="data">
  115. <slot :name="name" v-bind="data"></slot>
  116. </template>
  117. </q-table>
  118. </template>
  119. <script setup>
  120. import { ref, onMounted, toRaw, watch } from "vue";
  121. import { useRouter } from "vue-router";
  122. const emit = defineEmits(["onRowClick", "onAddItem", "noRows"]);
  123. const props = defineProps({
  124. // colunas de configuração da tabela
  125. columns: {
  126. type: Array,
  127. required: true,
  128. },
  129. // rota da api, ex: /clientes
  130. apiCall: {
  131. type: Function,
  132. required: true,
  133. },
  134. // botao de adicionar com aparencia de outline
  135. outlineAdd: {
  136. type: Boolean,
  137. default: false,
  138. },
  139. // ir para sub pagina on row click
  140. openItem: {
  141. type: Boolean,
  142. default: false,
  143. },
  144. // rota da sub page
  145. openItemRoute: {
  146. type: String,
  147. default: "",
  148. },
  149. // botao de adicionar
  150. addItem: {
  151. type: Boolean,
  152. default: true,
  153. },
  154. // botao de opcoes
  155. dropDown: {
  156. type: Boolean,
  157. default: false,
  158. },
  159. // botao de adicionar route
  160. addItemRoute: {
  161. type: String,
  162. default: "",
  163. },
  164. // quantidade de items por pagina
  165. rowsPerPage: {
  166. type: Number,
  167. default: 10,
  168. },
  169. comecarDesativado: {
  170. type: Boolean,
  171. default: false,
  172. },
  173. mostrarSelecaoDeColunas: {
  174. type: Boolean,
  175. default: false,
  176. },
  177. mostrarBotaoFullscreen: {
  178. type: Boolean,
  179. default: false,
  180. },
  181. // mostrarToggleInativos: {
  182. // type: Boolean,
  183. // default: false,
  184. // },
  185. mostrarCampoPesquisa: {
  186. type: Boolean,
  187. default: true,
  188. },
  189. noApiCall: {
  190. type: Boolean,
  191. default: false,
  192. },
  193. hideNoDataLabel: {
  194. type: Boolean,
  195. default: false,
  196. },
  197. // labelInativo: {
  198. // type: String,
  199. // default: "Exibir inativos",
  200. // },
  201. });
  202. const router = useRouter();
  203. const rows = ref([]);
  204. const filter = ref("");
  205. const loading = ref(true);
  206. const fullscreen = ref(false);
  207. const showInativos = ref(false);
  208. const inativos = ref([]);
  209. const getPaginationLabel = (from, to, last) => {
  210. return `${from}-${to} de ${last}`;
  211. };
  212. watch(showInativos, () => {
  213. if (showInativos.value) {
  214. rows.value = rows.value.concat(inativos.value);
  215. } else {
  216. inativos.value = rows.value.filter(
  217. (row) => row.status === false || row.ativo === false,
  218. );
  219. rows.value = rows.value.filter((row) => row.ativo);
  220. }
  221. });
  222. watch(
  223. () => props.apiCall,
  224. async () => {
  225. await onRequest();
  226. },
  227. );
  228. // remove as colunas obrigatórias do filtro de colunas
  229. const mapColuns = props.columns.reduce((accm, column) => {
  230. if (!column.required) {
  231. accm.push(column);
  232. }
  233. return accm;
  234. }, []);
  235. // as colunas que serão carregadas
  236. const visibleColumns = ref(mapColuns.map((column) => column.name));
  237. const onRowClick = (evt, row, index) => {
  238. const item = toRaw(row);
  239. if (props.openItem) {
  240. if (props.openItemRoute) {
  241. router.push({ name: props.openItemRoute, params: { id: item.id } });
  242. } else {
  243. emit("onRowClick", { evt, row, index });
  244. }
  245. }
  246. };
  247. const onAddItem = () => {
  248. if (props.addItem) {
  249. if (props.addItemRoute) {
  250. router.push({ name: props.addItemRoute });
  251. } else {
  252. emit("onAddItem");
  253. }
  254. }
  255. };
  256. // busca os dados do banco com filtros e pagination
  257. const onRequest = async () => {
  258. // const filter = params.filter;
  259. if (props.noApiCall) {
  260. loading.value = false;
  261. return;
  262. }
  263. // inicia o loading
  264. loading.value = true;
  265. // pega os dados do servidor
  266. const response = await props.apiCall();
  267. // limpa os dados atuais e adiciona os novos
  268. rows.value.splice(0, rows.value.length, ...response);
  269. // if (props.mostrarToggleInativos && !showInativos.value) {
  270. // inativos.value = rows.value.filter(
  271. // (row) => row.status === false || row.ativo === false,
  272. // );
  273. // rows.value = rows.value.filter((row) => row.ativo || row.status === true);
  274. // }
  275. if (rows.value.length == 0) {
  276. emit("noRows");
  277. }
  278. // finaliza o loading
  279. loading.value = false;
  280. };
  281. onMounted(async () => {
  282. // faz a primeira requisição
  283. await onRequest({
  284. filter: undefined,
  285. });
  286. if (props.comecarDesativado) {
  287. visibleColumns.value = mapColuns
  288. .map((column) => column.required)
  289. .map((column) => column.name);
  290. }
  291. });
  292. </script>
  293. <style lang="scss">
  294. @import "src/css/table.scss";
  295. </style>