DefaultTable.vue 7.8 KB

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